Skip to content

Commit 5dc09a8

Browse files
authored
Implement randomization of choices within categories for dropdowns and checkboxes Fix #10923 (#10994)
* Implement randomization of choices within categories for dropdowns and checkboxes Fix #10923 * Add randomization properties to ItemValue class
1 parent 77784b4 commit 5dc09a8

File tree

3 files changed

+132
-8
lines changed

3 files changed

+132
-8
lines changed

packages/survey-core/src/helpers.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,14 +186,34 @@ export class Helpers {
186186
return this.checkIfValuesEqual(x, y, { ignoreOrder: ignoreOrder, caseSensitive: caseSensitive, trimStrings: trimStrings });
187187
}
188188
public static randomizeArray<T>(array: Array<T>, seed?: number): Array<T> {
189-
array.sort((a: any, b: any) => a.uniqueId - b.uniqueId);
190-
const random = mulberry32(seed || Date.now());
191-
for (var i = array.length - 1; i > 0; i--) {
192-
var j = Math.floor(random() * (i + 1));
193-
var temp = array[i];
194-
array[i] = array[j];
195-
array[j] = temp;
189+
190+
const shuffle = (array: Array<T>) => {
191+
array.sort((a: any, b: any) => a.uniqueId - b.uniqueId);
192+
const random = mulberry32(seed || Date.now());
193+
for (var i = array.length - 1; i > 0; i--) {
194+
var j = Math.floor(random() * (i + 1));
195+
[array[i], array[j]] = [array[j], array[i]];
196+
}
197+
};
198+
199+
const categories = {};
200+
for (let i = 0; i < array.length; i++) {
201+
const item:any = array[i];
202+
if (item.randomize === false) continue;
203+
const key = item.randomizeCategory || "";
204+
if (!categories[key]) categories[key] = { indices: [], items: [] };
205+
categories[key].indices.push(i);
206+
categories[key].items.push(item);
196207
}
208+
209+
for (const key in categories) {
210+
const { indices, items } = categories[key];
211+
shuffle(items);
212+
for (let i = 0; i < indices.length; i++) {
213+
array[indices[i]] = items[i];
214+
}
215+
}
216+
197217
return array;
198218
}
199219
public static getUnbindValue(value: any): any {

packages/survey-core/src/itemvalue.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,9 @@ export class ItemValue extends BaseAction implements ILocalizableOwner, IShortcu
527527
protected setLocTitle(val: LocalizableString): void {}
528528
protected setTitle(val: string): void {}
529529
@property({ defaultValue: "" }) icon: string;
530+
531+
@property() randomize: boolean;
532+
@property() randomizeCategory: string;
530533
}
531534

532535
Base.createItemValue = function (source: any, type?: string): any {
@@ -565,7 +568,9 @@ Serializer.addClass(
565568
visibleIf: (obj: ItemValue): boolean => {
566569
return !obj || obj.ownerPropertyName !== "rateValues";
567570
},
568-
}
571+
},
572+
{ name: "randomize:boolean", default: true, visible: false, locationInTable: "detail" },
573+
{ name: "randomizeCategory:string", visible: false, locationInTable: "detail" },
569574
],
570575
(value: any) => new ItemValue(value)
571576
);

packages/survey-core/tests/randomSeed_test.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,4 +295,103 @@ QUnit.test("matrixdropdown", (assert) => {
295295
assert.deepEqual(matrix.visibleRows[0].cells[1].question.visibleChoices.map((e: any) => e.value).join(), "9,4,7,2,8,1,5,3,6", "matrix row#0 col#1 order for seed 1234567");
296296
assert.deepEqual(matrix.visibleRows[1].cells[0].question.visibleChoices.map((e: any) => e.value).join(), "5,1,6,2,4,7,8,3,9", "matrix row#1 col#0 order for seed 1234567");
297297
assert.deepEqual(matrix.visibleRows[1].cells[1].question.visibleChoices.map((e: any) => e.value).join(), "9,4,7,2,8,1,5,3,6", "matrix row#1 col#1 order for seed 1234567");
298+
});
299+
300+
QUnit.test("randomize with category", (assert) => {
301+
const survey = new SurveyModel({
302+
elements: [
303+
{
304+
type: "dropdown",
305+
name: "q1",
306+
choices: [
307+
{ value: 1, randomizeCategory: "A" },
308+
{ value: 2, randomize: false },
309+
{ value: 3, randomizeCategory: "A" },
310+
{ value: 4, randomizeCategory: "A" },
311+
{ value: 5, randomizeCategory: "A" },
312+
{ value: 6, randomizeCategory: "B" },
313+
{ value: 7, randomizeCategory: "B" },
314+
{ value: 8, randomizeCategory: "B" },
315+
{ value: 9, randomizeCategory: "C" }
316+
],
317+
choicesOrder: "random"
318+
},
319+
{
320+
type: "checkbox",
321+
name: "q2",
322+
choices: [
323+
{ value: 1, randomizeCategory: "A" },
324+
{ value: 2, randomizeCategory: "A" },
325+
{ value: 3, randomizeCategory: "A" },
326+
{ value: 4, randomizeCategory: "A" },
327+
{ value: 5, randomize: false },
328+
{ value: 6, randomizeCategory: "B" },
329+
{ value: 7, randomizeCategory: "B" },
330+
{ value: 8, randomizeCategory: "B" },
331+
{ value: 9, randomizeCategory: "C" }
332+
],
333+
choicesOrder: "random"
334+
},
335+
{
336+
type: "matrixdropdown",
337+
name: "m1",
338+
columns: [
339+
{
340+
name: "m1c1",
341+
cellType: "dropdown",
342+
choices: [
343+
{ value: 1, randomizeCategory: "A" },
344+
{ value: 2, randomize: false },
345+
{ value: 3, randomizeCategory: "A" },
346+
{ value: 4, randomizeCategory: "A" },
347+
{ value: 5, randomizeCategory: "A" },
348+
{ value: 6, randomizeCategory: "B" },
349+
{ value: 7, randomizeCategory: "B" },
350+
{ value: 8, randomizeCategory: "B" },
351+
{ value: 9, randomizeCategory: "C" }
352+
],
353+
choicesOrder: "random"
354+
},
355+
{
356+
name: "m1c2",
357+
cellType: "dropdown",
358+
choices: [
359+
{ value: 1, randomizeCategory: "A" },
360+
{ value: 2, randomizeCategory: "A" },
361+
{ value: 3, randomizeCategory: "A" },
362+
{ value: 4, randomizeCategory: "A" },
363+
{ value: 5, randomize: false },
364+
{ value: 6, randomizeCategory: "B" },
365+
{ value: 7, randomizeCategory: "B" },
366+
{ value: 8, randomizeCategory: "B" },
367+
{ value: 9, randomizeCategory: "C" }
368+
],
369+
choicesOrder: "random"
370+
}
371+
],
372+
rows: [
373+
{ value: 1, randomizeCategory: "A" },
374+
{ value: 2, randomizeCategory: "A" },
375+
{ value: 3, randomizeCategory: "A" },
376+
{ value: 4, randomizeCategory: "A" },
377+
{ value: 5, randomize: false },
378+
{ value: 6, randomizeCategory: "B" },
379+
{ value: 7, randomizeCategory: "B" },
380+
{ value: 8, randomizeCategory: "B" },
381+
{ value: 9, randomizeCategory: "C" }
382+
],
383+
rowOrder: "random"
384+
},
385+
]
386+
});
387+
388+
const q1 = survey.getQuestionByName("q1");
389+
const q2 = survey.getQuestionByName("q2");
390+
const matrix = <QuestionMatrixDropdownModel>survey.getQuestionByName("m1");
391+
survey.randomSeed = 987654;
392+
assert.deepEqual(q1.visibleChoices.map((e: any) => e.value).join(), "3,2,5,1,4,8,6,7,9", "dropdown choices order");
393+
assert.deepEqual(q2.visibleChoices.map((e: any) => e.value).join(), "3,2,1,4,5,7,6,8,9", "checkbox choices order");
394+
assert.deepEqual(matrix.visibleRows.map(e => e.rowName).join(), "3,2,1,4,5,7,6,8,9", "Matrix Row order randomized");
395+
assert.deepEqual(matrix.visibleRows[0].cells[0].question.visibleChoices.map((e: any) => e.value).join(), "5,2,1,3,4,6,8,7,9", "matrix row#0 col#0 order");
396+
assert.deepEqual(matrix.visibleRows[0].cells[1].question.visibleChoices.map((e: any) => e.value).join(), "1,3,2,4,5,6,7,8,9", "matrix row#0 col#1 order");
298397
});

0 commit comments

Comments
 (0)