@projectcaluma/ember-form 11.0.0-beta.3 → 11.0.0-beta.7

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 CHANGED
@@ -1,3 +1,42 @@
1
+ # [@projectcaluma/ember-form-v11.0.0-beta.7](https://github.com/projectcaluma/ember-caluma/compare/@projectcaluma/ember-form-v11.0.0-beta.6...@projectcaluma/ember-form-v11.0.0-beta.7) (2022-02-03)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **validation:** sync format validator validation with backend ([ee66968](https://github.com/projectcaluma/ember-caluma/commit/ee66968230b9f0e4c5a4df8bdb3f8e58b44b5b82))
7
+
8
+
9
+ ### BREAKING CHANGES
10
+
11
+ * **validation:** Use the `formatValidators` property of the backend to store and read
12
+ format validators instead of the `meta.formatValidators` so the backend
13
+ validates as well. For more information on how to migrate check the
14
+ migration guide to v11.
15
+
16
+ # [@projectcaluma/ember-form-v11.0.0-beta.6](https://github.com/projectcaluma/ember-caluma/compare/@projectcaluma/ember-form-v11.0.0-beta.5...@projectcaluma/ember-form-v11.0.0-beta.6) (2022-02-02)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * **action-button:** hide field wrapper if action button is not displayed ([1d4b701](https://github.com/projectcaluma/ember-caluma/commit/1d4b701af35d48d072001f3295b905b1065daffe))
22
+ * render static questions ([28fff94](https://github.com/projectcaluma/ember-caluma/commit/28fff9487e4ad3153fe267db4b66208ad858aaac))
23
+ * **table:** fix styling of table fields ([30c5903](https://github.com/projectcaluma/ember-caluma/commit/30c590389ed82c37ebf666f1484501c66ca8f0bb))
24
+
25
+ # [@projectcaluma/ember-form-v11.0.0-beta.5](https://github.com/projectcaluma/ember-caluma/compare/@projectcaluma/ember-form-v11.0.0-beta.4...@projectcaluma/ember-form-v11.0.0-beta.5) (2022-02-01)
26
+
27
+
28
+ ### Bug Fixes
29
+
30
+ * **document-validity:** wait to validate until the UI has finished saving values ([9ec5330](https://github.com/projectcaluma/ember-caluma/commit/9ec5330905046604f95ab42985a29a5e0dc369a4))
31
+ * **form:** use component save task instead of field for loading indicator ([6de510d](https://github.com/projectcaluma/ember-caluma/commit/6de510d19a3608b7cd40092908d562a152ef03bd))
32
+
33
+ # [@projectcaluma/ember-form-v11.0.0-beta.4](https://github.com/projectcaluma/ember-caluma/compare/@projectcaluma/ember-form-v11.0.0-beta.3...@projectcaluma/ember-form-v11.0.0-beta.4) (2022-02-01)
34
+
35
+
36
+ ### Bug Fixes
37
+
38
+ * **form:** fix closing of info modal ([b46095a](https://github.com/projectcaluma/ember-caluma/commit/b46095aa3b31bc1b2035a0818778edaa8fd9c745))
39
+
1
40
  # [@projectcaluma/ember-form-v11.0.0-beta.3](https://github.com/projectcaluma/ember-caluma/compare/@projectcaluma/ember-form-v11.0.0-beta.2...@projectcaluma/ember-form-v11.0.0-beta.3) (2022-01-24)
2
41
 
3
42
 
@@ -4,7 +4,7 @@
4
4
  class="uk-icon-button"
5
5
  uk-icon="info"
6
6
  title={{t "caluma.form.info"}}
7
- {{on "click" this.showModal}}
7
+ {{on "click" (fn (mut this.modalVisible) true)}}
8
8
  >
9
9
  <span class="uk-hidden">{{t "caluma.form.info"}}</span>
10
10
  </button>
@@ -12,7 +12,7 @@
12
12
  <UkModal
13
13
  @visible={{this.modalVisible}}
14
14
  @stack={{true}}
15
- @onHide={{this.hideModal}}
15
+ @onHide={{fn (mut this.modalVisible) false}}
16
16
  as |modal|
17
17
  >
18
18
  <modal.body>
@@ -1,21 +1,6 @@
1
- import { action } from "@ember/object";
2
1
  import Component from "@glimmer/component";
3
2
  import { tracked } from "@glimmer/tracking";
4
3
 
5
4
  export default class CfFieldInfoComponent extends Component {
6
5
  @tracked modalVisible = false;
7
-
8
- @action
9
- showModal(e) {
10
- e.preventDefault();
11
-
12
- this.modalVisible = true;
13
- }
14
-
15
- @action
16
- hideModal(e) {
17
- e.preventDefault();
18
-
19
- this.modalVisible = false;
20
- }
21
6
  }
@@ -1,4 +1,4 @@
1
1
  <MarkdownToHtml
2
- @markdown={{@field.question.staticContent}}
2
+ @markdown={{@field.question.raw.staticContent}}
3
3
  @openLinksInNewWindow={{true}}
4
4
  />
@@ -1,4 +1,4 @@
1
- <table class="uk-table uk-table-divider">
1
+ <table class="uk-table uk-table-divider uk-margin-remove-vertical">
2
2
  <thead>
3
3
  <tr>
4
4
  {{#each this.columns as |column|}}
@@ -25,27 +25,27 @@
25
25
  class="uk-animation-fade uk-text-danger"
26
26
  />
27
27
  {{/if}}
28
- <button
29
- data-test-edit-row
30
- type="button"
31
- class="uk-button uk-button-link uk-flex-inline uk-margin-small-left"
28
+ <UkButton
29
+ @color="link"
30
+ @onClick={{fn this.edit document}}
32
31
  title={{t "caluma.form.edit"}}
33
- {{on "click" (fn this.edit document)}}
32
+ class="uk-flex-inline uk-margin-small-left"
33
+ data-test-edit-row
34
34
  >
35
35
  <UkIcon @icon="pencil" />
36
- <span class="uk-hidden">{{t "caluma.form.edit"}}</span>
37
- </button>
36
+ <span hidden>{{t "caluma.form.edit"}}</span>
37
+ </UkButton>
38
38
  {{#unless @disabled}}
39
- <button
40
- data-test-delete-row
41
- type="button"
42
- class="uk-button uk-button-link uk-flex-inline uk-margin-small-left"
39
+ <UkButton
40
+ @color="link"
41
+ @onClick={{fn (perform this.delete) document}}
43
42
  title={{t "caluma.form.delete"}}
44
- {{on "click" (fn (perform this.delete) document)}}
43
+ class="uk-flex-inline uk-margin-small-left"
44
+ data-test-delete-row
45
45
  >
46
46
  <UkIcon @icon="trash" />
47
- <span class="uk-hidden">{{t "caluma.form.delete"}}</span>
48
- </button>
47
+ <span hidden>{{t "caluma.form.delete"}}</span>
48
+ </UkButton>
49
49
  {{/unless}}
50
50
  </div>
51
51
  </td>
@@ -57,13 +57,13 @@
57
57
  <tr>
58
58
  <td colspan={{add this.columns.length 1}} class="uk-text-center">
59
59
  <UkButton
60
- @size="small"
61
- @color="default"
60
+ @color="link"
62
61
  @onClick={{perform this.add}}
62
+ title={{t "caluma.form.addRow"}}
63
63
  data-test-add-row
64
64
  >
65
65
  <UkIcon @icon="plus" />
66
- <span class="uk-hidden">{{t "caluma.form.addRow"}}</span>
66
+ <span hidden>{{t "caluma.form.addRow"}}</span>
67
67
  </UkButton>
68
68
  </td>
69
69
  </tr>
@@ -86,7 +86,7 @@
86
86
  />
87
87
  </modal.body>
88
88
 
89
- <modal.footer @class="uk-text-right">
89
+ <modal.footer class="uk-text-right">
90
90
  {{#if @disabled}}
91
91
  <UkButton
92
92
  @label={{t "caluma.form.close"}}
@@ -1,5 +1,9 @@
1
1
  {{#if this.visible}}
2
- <div class="uk-margin">
2
+ <div
3
+ class="uk-margin"
4
+ {{did-insert this.registerComponent}}
5
+ {{will-destroy this.unregisterComponent}}
6
+ >
3
7
  {{#if this.labelVisible}}
4
8
  <CfField::label @field={{@field}} />
5
9
  {{/if}}
@@ -24,11 +28,11 @@
24
28
  <div
25
29
  class="cf-field__icon uk-padding-remove-vertical uk-flex uk-flex-middle uk-flex-center"
26
30
  >
27
- {{#if @field.save.isRunning}}
31
+ {{#if this.save.isRunning}}
28
32
  <UkSpinner class="uk-animation-fade" />
29
- {{else if (or @field.save.last.isError @field.isInvalid)}}
33
+ {{else if (or this.save.last.isError @field.isInvalid)}}
30
34
  <UkIcon @icon="warning" class="uk-animation-fade uk-text-danger" />
31
- {{else if @field.save.last.isSuccessful}}
35
+ {{else if this.save.last.isSuccessful}}
32
36
  <UkIcon @icon="check" class="uk-animation-fade uk-text-success" />
33
37
  {{/if}}
34
38
  </div>
@@ -1,5 +1,5 @@
1
1
  import { getOwner } from "@ember/application";
2
- import { set } from "@ember/object";
2
+ import { action } from "@ember/object";
3
3
  import Component from "@glimmer/component";
4
4
  import { timeout, restartableTask } from "ember-concurrency";
5
5
 
@@ -18,10 +18,24 @@ import { hasQuestionType } from "@projectcaluma/ember-core/helpers/has-question-
18
18
  * @argument {Field} field The field data model to render
19
19
  */
20
20
  export default class CfFieldComponent extends Component {
21
+ @action
22
+ registerComponent() {
23
+ this.args.field._components.add(this);
24
+ }
25
+
26
+ @action
27
+ unregisterComponent() {
28
+ this.args.field._components.delete(this);
29
+ }
30
+
21
31
  get visible() {
22
32
  return (
23
33
  !this.args.field?.hidden &&
24
- !hasQuestionType(this.args.field?.question, "form")
34
+ !hasQuestionType(this.args.field?.question, "form") &&
35
+ !(
36
+ hasQuestionType(this.args.field?.question, "action-button") &&
37
+ this.args.disabled
38
+ )
25
39
  );
26
40
  }
27
41
 
@@ -41,8 +55,9 @@ export default class CfFieldComponent extends Component {
41
55
  }
42
56
 
43
57
  /**
44
- * Task to save a field. This will set the passed value to the answer and
45
- * save the field to the API after a timeout off 500 milliseconds.
58
+ * Task to save a field. This will set the passed value to the answer and save
59
+ * the field to the API after a timeout of 500 milliseconds which intends to
60
+ * reduce the amount of saved values when changed rapidly.
46
61
  *
47
62
  * @method save
48
63
  * @param {String|Number|String[]} value
@@ -57,14 +72,18 @@ export default class CfFieldComponent extends Component {
57
72
  yield timeout(500);
58
73
  }
59
74
 
60
- set(this.args.field.answer, "value", value);
75
+ if (this.args.field.answer) {
76
+ this.args.field.answer.value = value;
77
+ }
61
78
 
62
79
  yield this.args.field.validate.perform();
63
80
 
64
81
  try {
82
+ // Save the new field value unlinked so the fields save task is not
83
+ // aborted when this component is destroyed
65
84
  return yield this.args.field.save.unlinked().perform();
66
85
  } catch (e) {
67
- // that's ok
86
+ // The component was destroyed before the fields save task was finished
68
87
  }
69
88
  }
70
89
  }
@@ -1,6 +1,7 @@
1
1
  import { action } from "@ember/object";
2
2
  import Component from "@glimmer/component";
3
3
  import { restartableTask } from "ember-concurrency";
4
+ import { cached } from "tracked-toolbox";
4
5
 
5
6
  /**
6
7
  * Component to check the validity of a document
@@ -31,12 +32,26 @@ export default class DocumentValidity extends Component {
31
32
  * @argument {Boolean} validateOnEnter
32
33
  */
33
34
 
35
+ @cached
34
36
  get isValid() {
35
- return this.args.document.fields.every((f) => f.isValid);
37
+ return this.args.document.fields
38
+ .filter((f) => !f.hidden)
39
+ .every((f) => f.isValid);
36
40
  }
37
41
 
38
42
  @restartableTask
39
43
  *_validate() {
44
+ const saveTasks = this.args.document.fields
45
+ .flatMap((field) => [
46
+ ...[...(field._components ?? [])].map((c) => c.save.last),
47
+ field.save?.last,
48
+ ])
49
+ .filter(Boolean);
50
+
51
+ // Wait until all currently running save tasks in the UI and in the field
52
+ // itself are finished
53
+ yield Promise.all(saveTasks);
54
+
40
55
  for (const field of this.args.document.fields) {
41
56
  yield field.validate.linked().perform();
42
57
  }
@@ -19,6 +19,15 @@ fragment SimpleQuestion on Question {
19
19
  value
20
20
  }
21
21
  placeholder
22
+ formatValidators {
23
+ edges {
24
+ node {
25
+ slug
26
+ regex
27
+ errorMsg
28
+ }
29
+ }
30
+ }
22
31
  }
23
32
  ... on TextareaQuestion {
24
33
  textareaMinLength: minLength
@@ -28,6 +37,15 @@ fragment SimpleQuestion on Question {
28
37
  value
29
38
  }
30
39
  placeholder
40
+ formatValidators {
41
+ edges {
42
+ node {
43
+ slug
44
+ regex
45
+ errorMsg
46
+ }
47
+ }
48
+ }
31
49
  }
32
50
  ... on IntegerQuestion {
33
51
  integerMinValue: minValue
@@ -3,6 +3,7 @@ import { assert } from "@ember/debug";
3
3
  import { associateDestroyableChild } from "@ember/destroyable";
4
4
  import { inject as service } from "@ember/service";
5
5
  import { camelize } from "@ember/string";
6
+ import { isEmpty } from "@ember/utils";
6
7
  import { tracked } from "@glimmer/tracking";
7
8
  import { queryManager } from "ember-apollo-client";
8
9
  import { restartableTask, lastValue, dropTask } from "ember-concurrency";
@@ -63,7 +64,6 @@ const fieldIsHiddenOrEmpty = (field) => {
63
64
  */
64
65
  export default class Field extends Base {
65
66
  @service intl;
66
- @service validator;
67
67
 
68
68
  @queryManager apollo;
69
69
 
@@ -152,6 +152,16 @@ export default class Field extends Base {
152
152
  */
153
153
  @tracked _errors = [];
154
154
 
155
+ /**
156
+ * Currently rendered field components that use this field. This is used in
157
+ * the document validity component to await all current save tasks before
158
+ * validating.
159
+ *
160
+ * @property {Set} _components
161
+ * @private
162
+ */
163
+ _components = new Set();
164
+
155
165
  /**
156
166
  * The primary key of the field. Consists of the document and question primary
157
167
  * keys.
@@ -618,6 +628,35 @@ export default class Field extends Base {
618
628
  this._errors = errors;
619
629
  }
620
630
 
631
+ /**
632
+ * Validate the value against the regexes of the given format validators.
633
+ *
634
+ * @method _validateFormatValidators
635
+ * @return {Array<Boolean|Object>} An array of error objects or `true`
636
+ * @private
637
+ */
638
+ _validateFormatValidators() {
639
+ const validators =
640
+ this.question.raw.formatValidators?.edges.map((edge) => edge.node) ?? [];
641
+ const value = this.answer.value;
642
+
643
+ if (isEmpty(value)) {
644
+ // empty values should not be validated since they are handled by the
645
+ // requiredness validation
646
+ return validators.map(() => true);
647
+ }
648
+
649
+ return validators.map((validator) => {
650
+ return (
651
+ new RegExp(validator.regex).test(value) || {
652
+ type: "format",
653
+ context: { errorMsg: validator.errorMsg },
654
+ value,
655
+ }
656
+ );
657
+ });
658
+ }
659
+
621
660
  /**
622
661
  * Method to validate if a question is required or not.
623
662
  *
@@ -637,15 +676,12 @@ export default class Field extends Base {
637
676
  * predefined by the question.
638
677
  *
639
678
  * @method _validateTextQuestion
640
- * @return {Promise<Boolean|Object>} A promise which resolves into an object if invalid or true if valid
679
+ * @return {Array<Boolean|Object>} An array of error objects or `true`
641
680
  * @private
642
681
  */
643
- async _validateTextQuestion() {
682
+ _validateTextQuestion() {
644
683
  return [
645
- ...(await this.validator.validate(
646
- this.answer.value,
647
- this.question.raw.meta.formatValidators ?? []
648
- )),
684
+ ...this._validateFormatValidators(),
649
685
  validate("length", this.answer.value, {
650
686
  min: this.question.raw.textMinLength || 0,
651
687
  max: this.question.raw.textMaxLength || Number.POSITIVE_INFINITY,
@@ -658,15 +694,12 @@ export default class Field extends Base {
658
694
  * than predefined by the question.
659
695
  *
660
696
  * @method _validateTextareaQuestion
661
- * @return {Promise<Boolean|Object>} A promise which resolves into an object if invalid or true if valid
697
+ * @return {Array<Boolean|Object>} An array of error objects or `true`
662
698
  * @private
663
699
  */
664
- async _validateTextareaQuestion() {
700
+ _validateTextareaQuestion() {
665
701
  return [
666
- ...(await this.validator.validate(
667
- this.answer.value,
668
- this.question.raw.meta.formatValidators ?? []
669
- )),
702
+ ...this._validateFormatValidators(),
670
703
  validate("length", this.answer.value, {
671
704
  min: this.question.raw.textareaMinLength || 0,
672
705
  max: this.question.raw.textareaMaxLength || Number.POSITIVE_INFINITY,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectcaluma/ember-form",
3
- "version": "11.0.0-beta.3",
3
+ "version": "11.0.0-beta.7",
4
4
  "description": "Ember addon for rendering Caluma forms.",
5
5
  "keywords": [
6
6
  "ember-addon"
@@ -16,7 +16,7 @@
16
16
  "dependencies": {
17
17
  "@glimmer/component": "^1.0.4",
18
18
  "@glimmer/tracking": "^1.0.4",
19
- "@projectcaluma/ember-core": "^11.0.0-beta.2",
19
+ "@projectcaluma/ember-core": "^11.0.0-beta.3",
20
20
  "ember-apollo-client": "^3.2.0",
21
21
  "ember-auto-import": "^2.4.0",
22
22
  "ember-autoresize-modifier": "^0.5.0",
@@ -24,15 +24,16 @@
24
24
  "ember-cli-htmlbars": "^6.0.1",
25
25
  "ember-cli-showdown": "^6.0.1",
26
26
  "ember-composable-helpers": "^5.0.0",
27
+ "ember-concurrency": "^2.2.0",
27
28
  "ember-fetch": "^8.1.1",
28
29
  "ember-in-viewport": "^4.0.0",
29
30
  "ember-intl": "^5.7.2",
30
- "ember-math-helpers": "^2.18.0",
31
+ "ember-math-helpers": "^2.18.1",
31
32
  "ember-pikaday": "^3.0.0",
32
33
  "ember-power-select": "^5.0.3",
33
- "ember-resources": "^4.1.3",
34
- "ember-uikit": "^5.0.0-beta.3",
35
- "ember-validators": "^4.0.1",
34
+ "ember-resources": "^4.2.0",
35
+ "ember-uikit": "^5.0.0-beta.11",
36
+ "ember-validators": "^4.1.0",
36
37
  "graphql": "^15.8.0",
37
38
  "jexl": "^2.3.0",
38
39
  "lodash.isequal": "^4.5.0",
@@ -43,9 +44,9 @@
43
44
  "@ember/optional-features": "2.0.0",
44
45
  "@ember/test-helpers": "2.6.0",
45
46
  "@embroider/test-setup": "1.0.0",
46
- "@faker-js/faker": "6.0.0-alpha.3",
47
- "@projectcaluma/ember-testing": "10.2.0-beta.2",
48
- "@projectcaluma/ember-workflow": "11.0.0-beta.1",
47
+ "@faker-js/faker": "6.0.0-alpha.5",
48
+ "@projectcaluma/ember-testing": "11.0.0-beta.1",
49
+ "@projectcaluma/ember-workflow": "11.0.0-beta.3",
49
50
  "broccoli-asset-rev": "3.0.0",
50
51
  "ember-cli": "3.28.5",
51
52
  "ember-cli-code-coverage": "1.0.3",
@@ -69,7 +70,7 @@
69
70
  "qunit": "2.17.2",
70
71
  "qunit-dom": "2.0.0",
71
72
  "uuid": "8.3.2",
72
- "webpack": "5.67.0"
73
+ "webpack": "5.68.0"
73
74
  },
74
75
  "engines": {
75
76
  "node": "12.* || 14.* || >= 16"