cx 26.5.1 → 26.6.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/build/locale/de-de.js +1 -0
- package/build/locale/en-us.js +1 -0
- package/build/locale/es-es.js +1 -0
- package/build/locale/fr-fr.js +1 -0
- package/build/locale/nl-nl.js +1 -0
- package/build/locale/pt-pt.js +1 -0
- package/build/locale/sr-latn-ba.js +1 -0
- package/build/widgets/form/LookupField.d.ts +6 -0
- package/build/widgets/form/LookupField.d.ts.map +1 -1
- package/build/widgets/form/LookupField.js +12 -0
- package/build/widgets/grid/Grid.d.ts +5 -0
- package/build/widgets/grid/Grid.d.ts.map +1 -1
- package/build/widgets/grid/Grid.js +3 -2
- package/dist/manifest.js +774 -774
- package/dist/widgets.css +4 -0
- package/dist/widgets.js +18 -2
- package/package.json +1 -1
- package/src/locale/de-de.ts +1 -0
- package/src/locale/en-us.ts +1 -0
- package/src/locale/es-es.ts +1 -0
- package/src/locale/fr-fr.ts +1 -0
- package/src/locale/nl-nl.ts +1 -0
- package/src/locale/pt-pt.ts +1 -0
- package/src/locale/sr-latn-ba.ts +1 -0
- package/src/widgets/form/LookupField.spec.tsx +149 -0
- package/src/widgets/form/LookupField.tsx +27 -0
- package/src/widgets/grid/Grid.scss +6 -0
- package/src/widgets/grid/Grid.tsx +8 -2
package/dist/widgets.css
CHANGED
|
@@ -5313,6 +5313,10 @@ th.cxe-calendar-display {
|
|
|
5313
5313
|
content: counter(cx-row-number);
|
|
5314
5314
|
}
|
|
5315
5315
|
|
|
5316
|
+
.cxe-grid-group-caption.cxs-reset-row-numbers {
|
|
5317
|
+
counter-set: cx-row-number 0;
|
|
5318
|
+
}
|
|
5319
|
+
|
|
5316
5320
|
.cxe-grid-fixed-header {
|
|
5317
5321
|
overflow: hidden;
|
|
5318
5322
|
position: absolute;
|
package/dist/widgets.js
CHANGED
|
@@ -8677,6 +8677,19 @@ class LookupField extends Field {
|
|
|
8677
8677
|
}
|
|
8678
8678
|
}
|
|
8679
8679
|
instance.lastDropdown = context.lastDropdown;
|
|
8680
|
+
if (this.validateOptionExists && isArray(data.options) && !this.isEmpty(data)) {
|
|
8681
|
+
let invalid = this.multiple
|
|
8682
|
+
? isArray(data.values) && data.records.length < data.values.length
|
|
8683
|
+
: !data.options.some(($option) =>
|
|
8684
|
+
areKeysEqual(
|
|
8685
|
+
getOptionKey(this.keyBindings, {
|
|
8686
|
+
$option,
|
|
8687
|
+
}),
|
|
8688
|
+
data.selectedKeys[0],
|
|
8689
|
+
),
|
|
8690
|
+
);
|
|
8691
|
+
if (invalid) data.error = this.invalidOptionText;
|
|
8692
|
+
}
|
|
8680
8693
|
super.prepareData(context, instance);
|
|
8681
8694
|
}
|
|
8682
8695
|
renderInput(context, instance, key) {
|
|
@@ -8742,6 +8755,8 @@ LookupField.prototype.hideSearchField = false;
|
|
|
8742
8755
|
LookupField.prototype.minOptionsForSearchField = 7;
|
|
8743
8756
|
LookupField.prototype.loadingText = "Loading...";
|
|
8744
8757
|
LookupField.prototype.queryErrorText = "Error occurred while querying for lookup data.";
|
|
8758
|
+
LookupField.prototype.validateOptionExists = false;
|
|
8759
|
+
LookupField.prototype.invalidOptionText = "The selected option is no longer available.";
|
|
8745
8760
|
LookupField.prototype.noResultsText = "No results found.";
|
|
8746
8761
|
LookupField.prototype.optionIdField = "id";
|
|
8747
8762
|
LookupField.prototype.optionTextField = "text";
|
|
@@ -15600,12 +15615,13 @@ class Grid extends ContainerBase {
|
|
|
15600
15615
|
renderGroupHeader(context, instance, g, level, group, i, store, fixedColumns) {
|
|
15601
15616
|
let { CSS, baseClass } = this;
|
|
15602
15617
|
let data = store.getData();
|
|
15618
|
+
let resetRowNumbers = g.resetRowNumbers ? "reset-row-numbers" : null;
|
|
15603
15619
|
if (g.caption) {
|
|
15604
15620
|
let caption = g.caption(data);
|
|
15605
15621
|
return jsx(
|
|
15606
15622
|
"tbody",
|
|
15607
15623
|
{
|
|
15608
|
-
className: CSS.element(baseClass, "group-caption", ["level-" + level]),
|
|
15624
|
+
className: CSS.element(baseClass, "group-caption", ["level-" + level, resetRowNumbers]),
|
|
15609
15625
|
"data-group-key": group.$key,
|
|
15610
15626
|
"data-group-element": `group-caption-${level}`,
|
|
15611
15627
|
children: jsx("tr", {
|
|
@@ -15696,7 +15712,7 @@ class Grid extends ContainerBase {
|
|
|
15696
15712
|
return jsx(
|
|
15697
15713
|
"tbody",
|
|
15698
15714
|
{
|
|
15699
|
-
className: CSS.element(baseClass, "group-caption", ["level-" + level]),
|
|
15715
|
+
className: CSS.element(baseClass, "group-caption", ["level-" + level, resetRowNumbers]),
|
|
15700
15716
|
"data-group-key": group.$key,
|
|
15701
15717
|
"data-group-element": `group-caption-${level}`,
|
|
15702
15718
|
children: lines,
|
package/package.json
CHANGED
package/src/locale/de-de.ts
CHANGED
|
@@ -16,6 +16,7 @@ Localization.localize(c, "cx/widgets/LookupField", {
|
|
|
16
16
|
queryErrorText: "Bei der Abfrage der gesuchten Daten ist ein Felhler aufgetreten.",
|
|
17
17
|
noResultsText: "Keine Ergebnisse gefunden.",
|
|
18
18
|
minQueryLengthMessageText: "Geben Sie mindestens {0} Zeichen ein.",
|
|
19
|
+
invalidOptionText: "Die ausgewählte Option ist nicht mehr verfügbar.",
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
// In common for Calendar and MonthPicker
|
package/src/locale/en-us.ts
CHANGED
|
@@ -15,6 +15,7 @@ Localization.localize(c, "cx/widgets/LookupField", {
|
|
|
15
15
|
queryErrorText: "Error occurred while querying for lookup data.",
|
|
16
16
|
noResultsText: "No results found.",
|
|
17
17
|
minQueryLengthMessageText: "Type in at least {0} character(s).",
|
|
18
|
+
invalidOptionText: "The selected option is no longer available.",
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
// In common for Calendar and MonthPicker
|
package/src/locale/es-es.ts
CHANGED
|
@@ -16,6 +16,7 @@ Localization.localize(c, "cx/widgets/LookupField", {
|
|
|
16
16
|
queryErrorText: "Se produjo un error al consultar los datos de búsqueda.",
|
|
17
17
|
noResultsText: "No se han encontrado resultados.",
|
|
18
18
|
minQueryLengthMessageText: "Escriba al menos {0} caracteres.",
|
|
19
|
+
invalidOptionText: "La opción seleccionada ya no está disponible.",
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
// In common for Calendar and MonthPicker
|
package/src/locale/fr-fr.ts
CHANGED
|
@@ -16,6 +16,7 @@ Localization.localize(c, "cx/widgets/LookupField", {
|
|
|
16
16
|
queryErrorText: "Une erreur s'est produite lors de l'interrogation des données de recherche.",
|
|
17
17
|
noResultsText: "Aucun résultat trouvé.",
|
|
18
18
|
minQueryLengthMessageText: "Tapez au moins {0} caractère (s).",
|
|
19
|
+
invalidOptionText: "L'option sélectionnée n'est plus disponible.",
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
// In common for Calendar and MonthPicker
|
package/src/locale/nl-nl.ts
CHANGED
|
@@ -16,6 +16,7 @@ Localization.localize(c, "cx/widgets/LookupField", {
|
|
|
16
16
|
queryErrorText: "Er is een fout opgetreden bij het weergeven van gegevens.",
|
|
17
17
|
noResultsText: "Geen resultaten gevonden",
|
|
18
18
|
minQueryLengthMessageText: "Voer minimaal {0} tekens in.",
|
|
19
|
+
invalidOptionText: "De geselecteerde optie is niet meer beschikbaar.",
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
// In common for Calendar and MonthPicker
|
package/src/locale/pt-pt.ts
CHANGED
|
@@ -16,6 +16,7 @@ Localization.localize(c, "cx/widgets/LookupField", {
|
|
|
16
16
|
queryErrorText: "Ocorreu um erro ao consultar os dados de pesquisa.",
|
|
17
17
|
noResultsText: "Nenhum resultado encontrado.",
|
|
18
18
|
minQueryLengthMessageText: "Digite pelo menos {0} caractere(s).",
|
|
19
|
+
invalidOptionText: "A opção selecionada já não está disponível.",
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
// In common for Calendar and MonthPicker
|
package/src/locale/sr-latn-ba.ts
CHANGED
|
@@ -16,6 +16,7 @@ Localization.localize(c, "cx/widgets/LookupField", {
|
|
|
16
16
|
queryErrorText: "Došlo je do greške kod pribavljanja podataka za prikaz.",
|
|
17
17
|
noResultsText: "Rezultati nisu pronađeni.",
|
|
18
18
|
minQueryLengthMessageText: "Unesite najmanje {0} karakter(a).",
|
|
19
|
+
invalidOptionText: "Izabrana opcija više nije dostupna.",
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
// In common for Calendar and MonthPicker
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { createAccessorModelProxy } from "../../data/createAccessorModelProxy";
|
|
2
2
|
import { LookupField } from "./LookupField";
|
|
3
|
+
import { Store } from "../../data/Store";
|
|
4
|
+
import { ValidationGroup } from "./ValidationGroup";
|
|
5
|
+
import { bind } from "../../ui/bind";
|
|
6
|
+
import { createTestRenderer } from "../../util/test/createTestRenderer";
|
|
7
|
+
import assert from "assert";
|
|
3
8
|
|
|
4
9
|
interface User {
|
|
5
10
|
id: number;
|
|
@@ -90,4 +95,148 @@ describe("LookupField", () => {
|
|
|
90
95
|
</cx>
|
|
91
96
|
);
|
|
92
97
|
});
|
|
98
|
+
|
|
99
|
+
describe("validateOptionExists", () => {
|
|
100
|
+
const options = [
|
|
101
|
+
{ id: 1, text: "One" },
|
|
102
|
+
{ id: 2, text: "Two" },
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
it("reports an error when the selected value is missing from options", async () => {
|
|
106
|
+
let widget = (
|
|
107
|
+
<cx>
|
|
108
|
+
<ValidationGroup errors={bind("errors")}>
|
|
109
|
+
<LookupField
|
|
110
|
+
value={bind("value")}
|
|
111
|
+
text={bind("text")}
|
|
112
|
+
options={options}
|
|
113
|
+
validateOptionExists
|
|
114
|
+
/>
|
|
115
|
+
</ValidationGroup>
|
|
116
|
+
</cx>
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
let store = new Store();
|
|
120
|
+
store.set("value", 99);
|
|
121
|
+
store.set("text", "Stale");
|
|
122
|
+
|
|
123
|
+
await createTestRenderer(store, widget);
|
|
124
|
+
|
|
125
|
+
let errors = store.get("errors");
|
|
126
|
+
assert.equal(errors.length, 1);
|
|
127
|
+
assert.equal(errors[0].message, "The selected option is no longer available.");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("does not report an error when the selected value matches an option", async () => {
|
|
131
|
+
let widget = (
|
|
132
|
+
<cx>
|
|
133
|
+
<ValidationGroup errors={bind("errors")}>
|
|
134
|
+
<LookupField
|
|
135
|
+
value={bind("value")}
|
|
136
|
+
text={bind("text")}
|
|
137
|
+
options={options}
|
|
138
|
+
validateOptionExists
|
|
139
|
+
/>
|
|
140
|
+
</ValidationGroup>
|
|
141
|
+
</cx>
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
let store = new Store();
|
|
145
|
+
store.set("value", 1);
|
|
146
|
+
store.set("text", "One");
|
|
147
|
+
|
|
148
|
+
await createTestRenderer(store, widget);
|
|
149
|
+
|
|
150
|
+
let errors = store.get("errors");
|
|
151
|
+
assert.equal(errors.length, 0);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("does not report an error when the field is empty", async () => {
|
|
155
|
+
let widget = (
|
|
156
|
+
<cx>
|
|
157
|
+
<ValidationGroup errors={bind("errors")}>
|
|
158
|
+
<LookupField
|
|
159
|
+
value={bind("value")}
|
|
160
|
+
text={bind("text")}
|
|
161
|
+
options={options}
|
|
162
|
+
validateOptionExists
|
|
163
|
+
/>
|
|
164
|
+
</ValidationGroup>
|
|
165
|
+
</cx>
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
let store = new Store();
|
|
169
|
+
|
|
170
|
+
await createTestRenderer(store, widget);
|
|
171
|
+
|
|
172
|
+
let errors = store.get("errors");
|
|
173
|
+
assert.equal(errors.length, 0);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("does not report an error when options are not provided (server-side mode)", async () => {
|
|
177
|
+
let widget = (
|
|
178
|
+
<cx>
|
|
179
|
+
<ValidationGroup errors={bind("errors")}>
|
|
180
|
+
<LookupField
|
|
181
|
+
value={bind("value")}
|
|
182
|
+
text={bind("text")}
|
|
183
|
+
onQuery={() => []}
|
|
184
|
+
validateOptionExists
|
|
185
|
+
/>
|
|
186
|
+
</ValidationGroup>
|
|
187
|
+
</cx>
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
let store = new Store();
|
|
191
|
+
store.set("value", 99);
|
|
192
|
+
store.set("text", "Stale");
|
|
193
|
+
|
|
194
|
+
await createTestRenderer(store, widget);
|
|
195
|
+
|
|
196
|
+
let errors = store.get("errors");
|
|
197
|
+
assert.equal(errors.length, 0);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("reports an error in multiple mode when some ids are not in options", async () => {
|
|
201
|
+
let widget = (
|
|
202
|
+
<cx>
|
|
203
|
+
<ValidationGroup errors={bind("errors")}>
|
|
204
|
+
<LookupField
|
|
205
|
+
multiple
|
|
206
|
+
values={bind("values")}
|
|
207
|
+
options={options}
|
|
208
|
+
validateOptionExists
|
|
209
|
+
/>
|
|
210
|
+
</ValidationGroup>
|
|
211
|
+
</cx>
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
let store = new Store();
|
|
215
|
+
store.set("values", [1, 99]);
|
|
216
|
+
|
|
217
|
+
await createTestRenderer(store, widget);
|
|
218
|
+
|
|
219
|
+
let errors = store.get("errors");
|
|
220
|
+
assert.equal(errors.length, 1);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("does not validate by default (back-compat)", async () => {
|
|
224
|
+
let widget = (
|
|
225
|
+
<cx>
|
|
226
|
+
<ValidationGroup errors={bind("errors")}>
|
|
227
|
+
<LookupField value={bind("value")} text={bind("text")} options={options} />
|
|
228
|
+
</ValidationGroup>
|
|
229
|
+
</cx>
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
let store = new Store();
|
|
233
|
+
store.set("value", 99);
|
|
234
|
+
store.set("text", "Stale");
|
|
235
|
+
|
|
236
|
+
await createTestRenderer(store, widget);
|
|
237
|
+
|
|
238
|
+
let errors = store.get("errors");
|
|
239
|
+
assert.equal(errors.length, 0);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
93
242
|
});
|
|
@@ -117,6 +117,12 @@ interface LookupFieldBaseConfig<TOption = any> extends FieldConfig {
|
|
|
117
117
|
/** Error message displayed if server query throws an exception. */
|
|
118
118
|
queryErrorText?: string;
|
|
119
119
|
|
|
120
|
+
/** Set to `true` to report a validation error when the selected value is not present in `options`. Only applies when `options` is an array. Default is `false`. */
|
|
121
|
+
validateOptionExists?: boolean;
|
|
122
|
+
|
|
123
|
+
/** Error message displayed when the selected value is not present in `options`. */
|
|
124
|
+
invalidOptionText?: string;
|
|
125
|
+
|
|
120
126
|
/** Message to be displayed if no entries match the user query. */
|
|
121
127
|
noResultsText?: string;
|
|
122
128
|
|
|
@@ -292,6 +298,8 @@ export class LookupField<TOption = any, TRecord = any> extends Field<
|
|
|
292
298
|
declare public minOptionsForSearchField: number;
|
|
293
299
|
declare public loadingText: string;
|
|
294
300
|
declare public queryErrorText: string;
|
|
301
|
+
declare public validateOptionExists: boolean;
|
|
302
|
+
declare public invalidOptionText: string;
|
|
295
303
|
declare public noResultsText: string;
|
|
296
304
|
declare public optionIdField: string;
|
|
297
305
|
declare public optionTextField: string;
|
|
@@ -501,6 +509,22 @@ export class LookupField<TOption = any, TRecord = any> extends Field<
|
|
|
501
509
|
|
|
502
510
|
(instance as DropdownInstance).lastDropdown = context.lastDropdown;
|
|
503
511
|
|
|
512
|
+
if (
|
|
513
|
+
this.validateOptionExists &&
|
|
514
|
+
isArray(data.options) &&
|
|
515
|
+
!this.isEmpty(data)
|
|
516
|
+
) {
|
|
517
|
+
let invalid = this.multiple
|
|
518
|
+
? isArray(data.values) && data.records!.length < data.values.length
|
|
519
|
+
: !data.options.some(($option) =>
|
|
520
|
+
areKeysEqual(
|
|
521
|
+
getOptionKey(this.keyBindings!, { $option }),
|
|
522
|
+
data.selectedKeys[0],
|
|
523
|
+
),
|
|
524
|
+
);
|
|
525
|
+
if (invalid) data.error = this.invalidOptionText;
|
|
526
|
+
}
|
|
527
|
+
|
|
504
528
|
super.prepareData(context, instance);
|
|
505
529
|
}
|
|
506
530
|
|
|
@@ -604,6 +628,9 @@ LookupField.prototype.minOptionsForSearchField = 7;
|
|
|
604
628
|
LookupField.prototype.loadingText = "Loading...";
|
|
605
629
|
LookupField.prototype.queryErrorText =
|
|
606
630
|
"Error occurred while querying for lookup data.";
|
|
631
|
+
LookupField.prototype.validateOptionExists = false;
|
|
632
|
+
LookupField.prototype.invalidOptionText =
|
|
633
|
+
"The selected option is no longer available.";
|
|
607
634
|
LookupField.prototype.noResultsText = "No results found.";
|
|
608
635
|
LookupField.prototype.optionIdField = "id";
|
|
609
636
|
LookupField.prototype.optionTextField = "text";
|
|
@@ -184,6 +184,12 @@
|
|
|
184
184
|
content: counter(cx-row-number);
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
// counter-set (not counter-reset) updates the existing row-number counter in place; a
|
|
188
|
+
// counter-reset here would create a nested counter that the following data rows ignore.
|
|
189
|
+
.#{$element}#{$name}-group-caption.#{$state}reset-row-numbers {
|
|
190
|
+
counter-set: cx-row-number 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
187
193
|
.#{$element}#{$name}-fixed-header {
|
|
188
194
|
overflow: hidden;
|
|
189
195
|
position: absolute;
|
|
@@ -156,6 +156,11 @@ export interface GridGroupingConfig<T> {
|
|
|
156
156
|
showCaption?: boolean;
|
|
157
157
|
showFooter?: boolean;
|
|
158
158
|
showHeader?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Restart automatic row numbers (`cxe-grid-row-number`) from 1 at the beginning of each group
|
|
161
|
+
* at this grouping level instead of counting continuously across the whole grid. Defaults to `false`.
|
|
162
|
+
*/
|
|
163
|
+
resetRowNumbers?: boolean;
|
|
159
164
|
caption?: StringProp;
|
|
160
165
|
name?: StringProp;
|
|
161
166
|
text?: StringProp;
|
|
@@ -1510,12 +1515,13 @@ export class Grid<T = unknown> extends ContainerBase<GridConfig<T>, GridInstance
|
|
|
1510
1515
|
) {
|
|
1511
1516
|
let { CSS, baseClass } = this;
|
|
1512
1517
|
let data = store.getData();
|
|
1518
|
+
let resetRowNumbers = g.resetRowNumbers ? "reset-row-numbers" : null;
|
|
1513
1519
|
if (g.caption) {
|
|
1514
1520
|
let caption = g.caption(data);
|
|
1515
1521
|
return (
|
|
1516
1522
|
<tbody
|
|
1517
1523
|
key={`g-${level}-${group.$key}`}
|
|
1518
|
-
className={CSS.element(baseClass, "group-caption", ["level-" + level])}
|
|
1524
|
+
className={CSS.element(baseClass, "group-caption", ["level-" + level, resetRowNumbers])}
|
|
1519
1525
|
data-group-key={group.$key}
|
|
1520
1526
|
data-group-element={`group-caption-${level}`}
|
|
1521
1527
|
>
|
|
@@ -1598,7 +1604,7 @@ export class Grid<T = unknown> extends ContainerBase<GridConfig<T>, GridInstance
|
|
|
1598
1604
|
return (
|
|
1599
1605
|
<tbody
|
|
1600
1606
|
key={"c" + group.$key}
|
|
1601
|
-
className={CSS.element(baseClass, "group-caption", ["level-" + level])}
|
|
1607
|
+
className={CSS.element(baseClass, "group-caption", ["level-" + level, resetRowNumbers])}
|
|
1602
1608
|
data-group-key={group.$key}
|
|
1603
1609
|
data-group-element={`group-caption-${level}`}
|
|
1604
1610
|
>
|