Skip to content

Commit 2f37c57

Browse files
authored
Refactor free-sort plugin to use pointer events (#2893)
1 parent cb87df4 commit 2f37c57

File tree

3 files changed

+78
-115
lines changed

3 files changed

+78
-115
lines changed

.changeset/dull-dragons-wonder.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@jspsych/plugin-free-sort": patch
3+
---
4+
5+
Fix event handling on non-touch devices

packages/plugin-free-sort/src/index.ts

Lines changed: 27 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych";
22

3+
import { inside_ellipse, make_arr, random_coordinate, shuffle } from "./utils";
4+
35
const info = <const>{
46
name: "free-sort",
57
parameters: {
@@ -240,17 +242,15 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
240242
for (const x of make_arr(0, trial.sort_area_width - trial.stim_width, num_rows)) {
241243
for (const y of make_arr(0, trial.sort_area_height - trial.stim_height, num_rows)) {
242244
if (x > (trial.sort_area_width - trial.stim_width) * 0.5) {
243-
//r_coords.push({ x:x, y:y } )
244245
r_coords.push({
245246
x: x + trial.sort_area_width * (0.5 * trial.column_spread_factor),
246-
y: y,
247+
y,
247248
});
248249
} else {
249250
l_coords.push({
250251
x: x - trial.sort_area_width * (0.5 * trial.column_spread_factor),
251-
y: y,
252+
y,
252253
});
253-
//l_coords.push({ x:x, y:y } )
254254
}
255255
}
256256
}
@@ -267,7 +267,6 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
267267
stimuli = shuffle(stimuli);
268268
}
269269

270-
let inside = [];
271270
for (let i = 0; i < stimuli.length; i++) {
272271
var coords;
273272
if (trial.stim_starts_inside) {
@@ -312,21 +311,19 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
312311
x: coords.x,
313312
y: coords.y,
314313
});
315-
if (trial.stim_starts_inside) {
316-
inside.push(true);
317-
} else {
318-
inside.push(false);
319-
}
320314
}
315+
const inside = stimuli.map(() => trial.stim_starts_inside);
321316

322317
// moves within a trial
323-
let moves = [];
318+
const moves = [];
324319

325320
// are objects currently inside
326321
let cur_in = false;
327322

328323
// draggable items
329-
const draggables = display_element.querySelectorAll(".jspsych-free-sort-draggable");
324+
const draggables = Array.from(
325+
display_element.querySelectorAll<HTMLImageElement>(".jspsych-free-sort-draggable")
326+
);
330327

331328
// button (will show when all items are inside) and border (will change color)
332329
const border: HTMLElement = display_element.querySelector("#jspsych-free-sort-border");
@@ -345,48 +342,13 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
345342
trial.counter_text_finished;
346343
}
347344

348-
let start_event_name = "mousedown";
349-
let move_event_name = "mousemove";
350-
let end_event_name = "mouseup";
351-
if (typeof document.ontouchend !== "undefined") {
352-
// for touch devices
353-
start_event_name = "touchstart";
354-
move_event_name = "touchmove";
355-
end_event_name = "touchend";
356-
}
357-
358-
for (let i = 0; i < draggables.length; i++) {
359-
draggables[i].addEventListener(start_event_name, (event: MouseEvent | TouchEvent) => {
360-
let pageX: number;
361-
let pageY: number;
362-
if (event instanceof MouseEvent) {
363-
pageX = event.pageX;
364-
pageY = event.pageY;
365-
}
366-
//if (typeof document.ontouchend !== "undefined") {
367-
if (event instanceof TouchEvent) {
368-
// for touch devices
369-
event.preventDefault();
370-
const touchObject = event.changedTouches[0];
371-
pageX = touchObject.pageX;
372-
pageY = touchObject.pageY;
373-
}
374-
375-
let elem = event.currentTarget as HTMLImageElement;
376-
let x = pageX - elem.offsetLeft;
377-
let y = pageY - elem.offsetTop - window.scrollY;
378-
elem.style.transform = "scale(" + trial.scale_factor + "," + trial.scale_factor + ")";
379-
380-
let move_event = (e) => {
381-
let clientX = e.clientX;
382-
let clientY = e.clientY;
383-
if (typeof document.ontouchend !== "undefined") {
384-
// for touch devices
385-
const touchObject = e.changedTouches[0];
386-
clientX = touchObject.clientX;
387-
clientY = touchObject.clientY;
388-
}
345+
for (const draggable of draggables) {
346+
draggable.addEventListener("pointerdown", function ({ clientX: pageX, clientY: pageY }) {
347+
let x = pageX - this.offsetLeft;
348+
let y = pageY - this.offsetTop - window.scrollY;
349+
this.style.transform = "scale(" + trial.scale_factor + "," + trial.scale_factor + ")";
389350

351+
const on_pointer_move = ({ clientX, clientY }: PointerEvent) => {
390352
cur_in = inside_ellipse(
391353
clientX - x,
392354
clientY - y,
@@ -396,12 +358,12 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
396358
trial.sort_area_height * 0.5,
397359
trial.sort_area_shape == "square"
398360
);
399-
elem.style.top =
361+
this.style.top =
400362
Math.min(
401363
trial.sort_area_height - trial.stim_height * 0.5,
402364
Math.max(-trial.stim_height * 0.5, clientY - y)
403365
) + "px";
404-
elem.style.left =
366+
this.style.left =
405367
Math.min(
406368
trial.sort_area_width * 1.5 - trial.stim_width,
407369
Math.max(-trial.sort_area_width * 0.5, clientX - x)
@@ -419,7 +381,7 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
419381
}
420382

421383
// replace in overall array, grab index from item id
422-
var elem_number = parseInt(elem.id.split("jspsych-free-sort-draggable-")[1], 10);
384+
var elem_number = parseInt(this.id.split("jspsych-free-sort-draggable-")[1], 10);
423385
inside.splice(elem_number, 1, cur_in);
424386

425387
// modify text and background if all items are inside
@@ -437,11 +399,11 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
437399
get_counter_text(inside.length - inside.filter(Boolean).length);
438400
}
439401
};
440-
document.addEventListener(move_event_name, move_event);
402+
document.addEventListener("pointermove", on_pointer_move);
441403

442-
var end_event = (e) => {
443-
document.removeEventListener(move_event_name, move_event);
444-
elem.style.transform = "scale(1, 1)";
404+
const on_pointer_up = (e) => {
405+
document.removeEventListener("pointermove", on_pointer_move);
406+
this.style.transform = "scale(1, 1)";
445407
if (trial.change_border_background_color) {
446408
if (inside.every(Boolean)) {
447409
border.style.background = trial.border_color_in;
@@ -452,13 +414,13 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
452414
}
453415
}
454416
moves.push({
455-
src: elem.dataset.src,
456-
x: elem.offsetLeft,
457-
y: elem.offsetTop,
417+
src: this.dataset.src,
418+
x: this.offsetLeft,
419+
y: this.offsetTop,
458420
});
459-
document.removeEventListener(end_event_name, end_event);
421+
document.removeEventListener("pointerup", on_pointer_up);
460422
};
461-
document.addEventListener(end_event_name, end_event);
423+
document.addEventListener("pointerup", on_pointer_up);
462424
});
463425
}
464426

@@ -507,56 +469,6 @@ class FreeSortPlugin implements JsPsychPlugin<Info> {
507469
}
508470
return text_out;
509471
}
510-
511-
// helper functions
512-
function shuffle(array) {
513-
// define three variables
514-
let cur_idx = array.length,
515-
tmp_val,
516-
rand_idx;
517-
518-
// While there remain elements to shuffle...
519-
while (0 !== cur_idx) {
520-
// Pick a remaining element...
521-
rand_idx = Math.floor(Math.random() * cur_idx);
522-
cur_idx -= 1;
523-
524-
// And swap it with the current element.
525-
tmp_val = array[cur_idx];
526-
array[cur_idx] = array[rand_idx];
527-
array[rand_idx] = tmp_val;
528-
}
529-
return array;
530-
}
531-
532-
function make_arr(startValue, stopValue, cardinality) {
533-
const step = (stopValue - startValue) / (cardinality - 1);
534-
let arr = [];
535-
for (let i = 0; i < cardinality; i++) {
536-
arr.push(startValue + step * i);
537-
}
538-
return arr;
539-
}
540-
541-
function inside_ellipse(x, y, x0, y0, rx, ry, square = false) {
542-
const results = [];
543-
if (square) {
544-
return Math.abs(x - x0) <= rx && Math.abs(y - y0) <= ry;
545-
} else {
546-
return (
547-
(x - x0) * (x - x0) * (ry * ry) + (y - y0) * (y - y0) * (rx * rx) <= rx * rx * (ry * ry)
548-
);
549-
}
550-
}
551-
552-
function random_coordinate(max_width, max_height) {
553-
const rnd_x = Math.floor(Math.random() * (max_width - 1));
554-
const rnd_y = Math.floor(Math.random() * (max_height - 1));
555-
return {
556-
x: rnd_x,
557-
y: rnd_y,
558-
};
559-
}
560472
}
561473
}
562474

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
export function shuffle(array) {
2+
// define three variables
3+
let cur_idx = array.length,
4+
tmp_val,
5+
rand_idx;
6+
7+
// While there remain elements to shuffle...
8+
while (0 !== cur_idx) {
9+
// Pick a remaining element...
10+
rand_idx = Math.floor(Math.random() * cur_idx);
11+
cur_idx -= 1;
12+
13+
// And swap it with the current element.
14+
tmp_val = array[cur_idx];
15+
array[cur_idx] = array[rand_idx];
16+
array[rand_idx] = tmp_val;
17+
}
18+
return array;
19+
}
20+
21+
export function make_arr(startValue, stopValue, cardinality) {
22+
const step = (stopValue - startValue) / (cardinality - 1);
23+
let arr = [];
24+
for (let i = 0; i < cardinality; i++) {
25+
arr.push(startValue + step * i);
26+
}
27+
return arr;
28+
}
29+
30+
export function inside_ellipse(x, y, x0, y0, rx, ry, square = false) {
31+
const results = [];
32+
if (square) {
33+
return Math.abs(x - x0) <= rx && Math.abs(y - y0) <= ry;
34+
} else {
35+
return (x - x0) * (x - x0) * (ry * ry) + (y - y0) * (y - y0) * (rx * rx) <= rx * rx * (ry * ry);
36+
}
37+
}
38+
39+
export function random_coordinate(max_width, max_height) {
40+
const rnd_x = Math.floor(Math.random() * (max_width - 1));
41+
const rnd_y = Math.floor(Math.random() * (max_height - 1));
42+
return {
43+
x: rnd_x,
44+
y: rnd_y,
45+
};
46+
}

0 commit comments

Comments
 (0)