Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Select Items to Rank - Allow users to move items between the ranked and unranked areas using a double click #8128

Merged
merged 19 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f7a09a3
work for the https://github.com/surveyjs/survey-library/issues/7834
dmitry-kurmanov Apr 15, 2024
ca61527
Merge branch 'master' into feature/7834-select-to-rank-by-double-click
dmitry-kurmanov Apr 16, 2024
b98ba71
work for the https://github.com/surveyjs/survey-library/issues/7834
dmitry-kurmanov Apr 16, 2024
a48786e
work for the https://github.com/surveyjs/survey-library/issues/7834
dmitry-kurmanov Apr 16, 2024
5824403
work for the https://github.com/surveyjs/survey-library/issues/7834
dmitry-kurmanov Apr 16, 2024
e7592ac
Merge branch 'master' into feature/7834-select-to-rank-by-double-click
dmitry-kurmanov Apr 16, 2024
5356e18
work for the https://github.com/surveyjs/survey-library/issues/7834
dmitry-kurmanov Apr 16, 2024
ec365ac
work for the https://github.com/surveyjs/survey-library/issues/7843
dmitry-kurmanov Apr 17, 2024
74a29d5
Merge branch 'master' into feature/7834-select-to-rank-by-double-click
dmitry-kurmanov Apr 17, 2024
d017647
work for the https://github.com/surveyjs/survey-library/issues/7834
dmitry-kurmanov Apr 18, 2024
ba39a0e
Merge branch 'master' into feature/7834-select-to-rank-by-double-click
dmitry-kurmanov Apr 18, 2024
8417607
work for the https://github.com/surveyjs/survey-library/issues/7834
dmitry-kurmanov Apr 18, 2024
67d6fa3
Merge branch 'master' into feature/7834-select-to-rank-by-double-click
dmitry-kurmanov Apr 19, 2024
849525f
work for the https://github.com/surveyjs/survey-library/issues/7834
dmitry-kurmanov Apr 19, 2024
563d8c6
Merge branch 'master' into feature/7834-select-to-rank-by-double-click
dmitry-kurmanov Apr 19, 2024
b13e6a9
work for the https://github.com/surveyjs/survey-library/issues/7834
dmitry-kurmanov Apr 19, 2024
937dba1
Merge branch 'master' into feature/7834-select-to-rank-by-double-click
dmitry-kurmanov Apr 22, 2024
cee1dc3
work for the https://github.com/surveyjs/survey-library/issues/7834
dmitry-kurmanov Apr 22, 2024
ffa52f9
Merge branch 'master' into feature/7834-select-to-rank-by-double-click
dmitry-kurmanov Apr 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
<div [attr.tabindex]="question.getItemTabIndex(model)" [attr.data-sv-drop-target-ranking-item]="index"
[class]="question.getItemClass(model)"
(keydown)="question.handleKeydown($event, model)"
(pointerdown)="question.handlePointerDown($event, model, $any($event.currentTarget))">
(pointerdown)="question.handlePointerDown($event, model, $any($event.currentTarget))"
(pointerup)="question.handlePointerUp($event, model, $any($event.currentTarget))">
<div tabindex="-1" style="outline: none;">
<div [class]="question.cssClasses.itemGhostNode"></div>
<div [class]="question.cssClasses.itemContent">
Expand Down
10 changes: 10 additions & 0 deletions packages/survey-vue3-ui/src/RankingItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@
);
}
"
v-on:pointerup="
(event) => {
question.handlePointerUp.call(
question,
event,
item,
event.currentTarget as HTMLElement
);
}
"
>
<div tabindex="-1" style="outline: none">
<div :class="question.cssClasses.itemGhostNode"></div>
Expand Down
5 changes: 5 additions & 0 deletions src/knockout/koquestion_ranking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export class QuestionRanking extends QuestionRankingModel {
this.handlePointerDown(event, data, <HTMLElement>event.currentTarget);
return true;
};
public koHandlePointerUp = (data: ItemValue, event: PointerEvent) => {
if(!this.survey.isDesignMode) event.preventDefault();
this.handlePointerUp(event, data, <HTMLElement>event.currentTarget);
return true;
};
}

Serializer.overrideClassCreator("ranking", function() {
Expand Down
2 changes: 1 addition & 1 deletion src/knockout/templates/question-ranking.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

<script type="text/html" id="survey-ranking-item">
<div
data-bind="event: { keydown: question.koHandleKeydown, pointerdown: question.koHandlePointerDown}, css: question.getItemClass($data), attr: {tabindex: question.getItemTabIndex($data), 'data-sv-drop-target-ranking-item': $index() }"
data-bind="event: { keydown: question.koHandleKeydown, pointerdown: question.koHandlePointerDown, pointerup: question.koHandlePointerUp}, css: question.getItemClass($data), attr: {tabindex: question.getItemTabIndex($data), 'data-sv-drop-target-ranking-item': $index() }"
>
<div tabindex="-1" style="outline: none;">
<div data-bind="css: question.cssClasses.itemGhostNode"></div>
Expand Down
55 changes: 43 additions & 12 deletions src/question_ranking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IsMobile } from "./utils/devices";
import { Helpers } from "./helpers";
import { settings } from "../src/settings";
import { AnimationGroup, IAnimationConsumer } from "./utils/animation";
import { DragOrClickHelper } from "./utils/dragOrClickHelper";

/**
* A class that describes the Ranking question type.
Expand All @@ -18,6 +19,7 @@ import { AnimationGroup, IAnimationConsumer } from "./utils/animation";
*/
export class QuestionRankingModel extends QuestionCheckboxModel {
private domNode: HTMLElement = null;
private dragOrClickHelper: DragOrClickHelper;

constructor(name: string) {
super(name);
Expand All @@ -28,6 +30,7 @@ export class QuestionRankingModel extends QuestionCheckboxModel {
this.setDragDropRankingChoices();
this.updateRankingChoicesSync();
});
this.dragOrClickHelper = new DragOrClickHelper(this.startDrag);
}

protected getDefaultItemComponent(): string {
Expand Down Expand Up @@ -337,6 +340,8 @@ export class QuestionRankingModel extends QuestionCheckboxModel {
return new DragDropRankingChoices(this.survey, null, this.longTap);
}

private draggedChoise: ItemValue;
private draggedTargetNode: HTMLElement;
public handlePointerDown = (
event: PointerEvent,
choice: ItemValue,
Expand All @@ -353,7 +358,26 @@ export class QuestionRankingModel extends QuestionCheckboxModel {
this.canStartDragDueItemEnabled(choice)
)
{
this.dragDropRankingChoices.startDrag(event, choice, this, node);
this.draggedChoise = choice;
this.draggedTargetNode = node;
this.dragOrClickHelper.onPointerDown(event);
}
};

public startDrag = (event: PointerEvent): void => {
this.dragDropRankingChoices.startDrag(event, this.draggedChoise, this, this.draggedTargetNode);
}

public handlePointerUp = (
event: PointerEvent,
choice: ItemValue,
node: HTMLElement
): void => {
if (!this.selectToRankEnabled) return;
if (
this.allowStartDrag
) {
this.handleKeydownSelectToRank(<any>event, choice, " ", false);
}
};

Expand Down Expand Up @@ -438,9 +462,11 @@ export class QuestionRankingModel extends QuestionCheckboxModel {
}, 1);
}

public handleKeydownSelectToRank(event: KeyboardEvent, movedElement: ItemValue): void {
public handleKeydownSelectToRank(event: KeyboardEvent, movedElement: ItemValue, hardKey?:string, isNeedFocus: boolean = true): void {
if (this.isDesignMode) return;
const key: any = event.key;

let key: any = event.key;
if (hardKey) key = hardKey;
if(key !== " " && key !== "ArrowUp" && key !== "ArrowDown") return;

const dnd:any = this.dragDropRankingChoices; //????
Expand All @@ -452,11 +478,12 @@ export class QuestionRankingModel extends QuestionCheckboxModel {
let toIndex;

if (key === " " && !isMovedElementRanked) {
toIndex = 0;
if (!this.checkMaxSelectedChoicesUnreached() || !this.canStartDragDueItemEnabled(movedElement)) return;
toIndex = this.value.length;
this.animationAllowed = false;
dnd.selectToRank(this, fromIndex, toIndex);
this.animationAllowed = true;
this.setValueAfterKeydown(toIndex, "to-container");
this.setValueAfterKeydown(toIndex, "to-container", isNeedFocus);
return;
}
if(!isMovedElementRanked) return;
Expand All @@ -465,23 +492,27 @@ export class QuestionRankingModel extends QuestionCheckboxModel {
dnd.unselectFromRank(this, fromIndex);
this.animationAllowed = true;
toIndex = this.unRankingChoices.indexOf(movedElement); //'this.' leads to actual array after the 'unselectFromRank' method
this.setValueAfterKeydown(toIndex, "from-container");
this.setValueAfterKeydown(toIndex, "from-container", isNeedFocus);
return;
}
const delta = key === "ArrowUp" ? -1 : (key === "ArrowDown" ? 1 : 0);
if(delta === 0) return;
toIndex = fromIndex + delta;
if(toIndex < 0 || toIndex >= rankingChoices.length) return;
dnd.reorderRankedItem(this, fromIndex, toIndex);
this.setValueAfterKeydown(toIndex, "to-container");
this.setValueAfterKeydown(toIndex, "to-container", isNeedFocus);
}

private setValueAfterKeydown(index: number, container: string) {
private setValueAfterKeydown(index: number, container: string, isNeedFocus: boolean = true) {
this.setValue();
setTimeout(() => {
this.focusItem(index, container);
}, 1);
event.preventDefault();

if (isNeedFocus) {
setTimeout(() => {
this.focusItem(index, container);
}, 1);
}

event && event.preventDefault();
}

private focusItem = (index: number, container?: string) => {
Expand Down
16 changes: 16 additions & 0 deletions src/react/reactquestion_ranking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ export class SurveyQuestionRanking extends SurveyQuestionElementBase {
event.currentTarget
);
},
(event: any) => {
event.persist();
//event.preventDefault();
this.question.handlePointerUp.call(
this.question,
event,
item,
event.currentTarget
);
},
this.question.cssClasses,
this.question.getItemClass(item),
this.question,
Expand All @@ -82,6 +92,7 @@ export class SurveyQuestionRanking extends SurveyQuestionElementBase {
i: number,
handleKeydown: (event: any) => void,
handlePointerDown: (event: PointerEvent) => void,
handlePointerUp: (event: PointerEvent) => void,
cssClasses: any,
itemClass: string,
question: QuestionRankingModel,
Expand All @@ -101,6 +112,7 @@ export class SurveyQuestionRanking extends SurveyQuestionElementBase {
itemTabIndex={tabIndex}
handleKeydown={handleKeydown}
handlePointerDown={handlePointerDown}
handlePointerUp={handlePointerUp}
cssClasses={cssClasses}
itemClass={itemClass}
question={question}
Expand Down Expand Up @@ -133,6 +145,9 @@ export class SurveyQuestionRankingItem extends ReactSurveyElement {
protected get handlePointerDown(): (event: any) => void {
return this.props.handlePointerDown;
}
protected get handlePointerUp(): (event: any) => void {
return this.props.handlePointerUp;
}
protected get cssClasses(): any {
return this.props.cssClasses;
}
Expand Down Expand Up @@ -167,6 +182,7 @@ export class SurveyQuestionRankingItem extends ReactSurveyElement {
className={this.itemClass}
onKeyDown={this.handleKeydown}
onPointerDown={this.handlePointerDown}
onPointerUp={this.handlePointerUp}
data-sv-drop-target-ranking-item={this.index}
>
<div tabIndex={-1} style={{ outline: "none" }}>
Expand Down
2 changes: 1 addition & 1 deletion src/vue/ranking/ranking-item.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div :tabindex="question.getItemTabIndex(item)" :data-sv-drop-target-ranking-item="index" :class="question.getItemClass(item)" v-on:keydown="(event)=>{question.handleKeydown.call(question, event, item)}" v-on:pointerdown="(event)=>{question.handlePointerDown.call(question, event, item, event.currentTarget)}">
<div :tabindex="question.getItemTabIndex(item)" :data-sv-drop-target-ranking-item="index" :class="question.getItemClass(item)" v-on:keydown="(event)=>{question.handleKeydown.call(question, event, item)}" v-on:pointerdown="(event)=>{question.handlePointerDown.call(question, event, item, event.currentTarget)}" v-on:pointerup="(event)=>{question.handlePointerUp.call(question, event, item, event.currentTarget)}">
<div tabindex="-1" style="outline: none;">
<div :class="cssClasses.itemGhostNode" />
<div :class="cssClasses.itemContent">
Expand Down
24 changes: 23 additions & 1 deletion testCafe/questions/ranking.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ frameworks.forEach((framework) => {
.find("span")
.withText("two");

await t.dragToElement(FirstItem, SecondItem);
await t.dragToElement(FirstItem, SecondItem, { speed: 0.1 });

let data = await getData();
await t.expect(data[newName]).eql([
Expand Down Expand Up @@ -311,4 +311,26 @@ frameworks.forEach((framework) => {

await removeFlexboxLayout();
});

test("ranking: selectToRank: click to add", async (t) => {
const setSelectToRankEnabled = ClientFunction(() => {
const rankingQ = window["survey"].getAllQuestions()[0];
rankingQ.selectToRankEnabled = true;
});
await setSelectToRankEnabled();
await t.click(PriceItem);
await t.click(BatteryItem);

let data = await getData();
await t.expect(data["smartphone-features"]).eql([
"Price",
"Battery life"
]);

const setSelectToRankDisabled = ClientFunction(() => {
const rankingQ = window["survey"].getAllQuestions()[0];
rankingQ.selectToRankEnabled = false;
});
await setSelectToRankDisabled();
});
});
18 changes: 17 additions & 1 deletion tests/question_ranking_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ QUnit.test("Ranking: selectToRank key navigation with animation", function (asse

q.handleKeydown(<any>{ key: " ", preventDefault: () => {} }, q.choices[0]);
assert.deepEqual(q.unRankingChoices.map((item) => item.value), ["c"]);
assert.deepEqual(q.rankingChoices.map((item) => item.value), ["a", "b"]);
assert.deepEqual(q.rankingChoices.map((item) => item.value), ["b", "a"]);

q.handleKeydown(<any>{ key: " ", preventDefault: () => {} }, q.choices[1]);
assert.deepEqual(q.unRankingChoices.map((item) => item.value), ["b", "c"]);
Expand Down Expand Up @@ -511,6 +511,22 @@ QUnit.test("selectToRankEnabled : checkMaxSelectedChoicesUnreached", function (a
assert.equal(questionModel.checkMaxSelectedChoicesUnreached(), false, "MaxSelectedChoices limit reached");
});

QUnit.test("selectToRankEnabled : checkMaxSelectedChoices and handleKeydownSelectToRank", function (assert) {
const selectToRankEnabled = true;
const withDefaultValue = true;
const questionModel = createRankingQuestionModel(selectToRankEnabled, withDefaultValue);

questionModel.maxSelectedChoices = 2;
const fakeEvent:any = { key: " ", preventDefault: ()=>{} };
questionModel.handleKeydownSelectToRank(fakeEvent, questionModel.unRankingChoices[0], " ", false);

assert.equal(questionModel.value.length, 2, "can't add due to MaxSelectedChoices");

questionModel.handleKeydownSelectToRank(fakeEvent, questionModel.rankingChoices[0], " ", false);

assert.equal(questionModel.value.length, 1, "unrank with MaxSelectedChoices");
});

QUnit.test("Ranking: renderedSelectToRankAreasLayout", function (assert) {
const selectToRankEnabled = true;
const withDefaultValue = false;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion visualRegressionTests/tests/defaultV2/ranking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ frameworks.forEach(framework => {
});

await patchDragDropToShowGhostElementAfterDrop();
await t.dragToElement(item1, item1);
await t.dragToElement(item1, item1, { destinationOffsetX: -1, speed: 0.1 });
await takeElementScreenshot("question-ranking-shortcut-position-container-scroll-layout.png", Selector(".sd-question"), t, comparer);
});
});
Expand Down
Loading