Skip to content

Commit 0224138

Browse files
committed
also allow setting callbacks
1 parent e2317c4 commit 0224138

File tree

3 files changed

+146
-16
lines changed

3 files changed

+146
-16
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ edition = "2021"
66
[dependencies]
77
js-sys = "0.3.60"
88
wasm-bindgen = "0.2.83"
9-
web-sys = { version = "0.3.60", features = ["Element"] }
9+
web-sys = { version = "0.3.60", features = ["Element", "HtmlElement"] }

examples/simple-list/src/main.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,22 @@ use wasm_bindgen::prelude::*;
22

33
fn main() {
44
web_sys::console::log_1(&JsValue::from_str("Wasm init complete"));
5-
sortable_js::Options::new()
5+
let the_sortable = sortable_js::Options::new()
66
.animation_ms(150.)
7+
.on_update(|e| {
8+
web_sys::console::log_2(
9+
&JsValue::from_str(&format!("got event: rust is {:?}, js is ", e)),
10+
&e.raw_event,
11+
)
12+
})
713
.apply(
814
&web_sys::window()
915
.expect("had no window")
1016
.document()
1117
.expect("had no document")
1218
.get_element_by_id("list")
13-
.expect("could not find #list")
19+
.expect("could not find #list"),
1420
);
21+
Box::leak(Box::new(the_sortable));
1522
web_sys::console::log_1(&JsValue::from_str("Wasm execution finished"));
1623
}

src/lib.rs

Lines changed: 136 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use wasm_bindgen::JsValue;
1+
use std::rc::Rc;
2+
3+
use wasm_bindgen::closure::Closure;
4+
use wasm_bindgen::{JsCast, JsValue};
25

36
mod js {
47
use wasm_bindgen::prelude::*;
@@ -12,26 +15,113 @@ mod js {
1215
}
1316
}
1417

18+
#[derive(Clone, Debug)]
19+
pub struct Event {
20+
pub raw_event: js_sys::Object,
21+
pub to: web_sys::HtmlElement,
22+
pub from: web_sys::HtmlElement,
23+
pub item: web_sys::HtmlElement,
24+
pub clone: web_sys::HtmlElement,
25+
pub old_index: Option<usize>,
26+
pub new_index: Option<usize>,
27+
pub old_draggable_index: Option<usize>,
28+
pub new_draggable_index: Option<usize>,
29+
// TODO: pullMode
30+
}
31+
32+
impl Event {
33+
fn from_raw_event(raw_event: js_sys::Object) -> Event {
34+
macro_rules! get {
35+
($field:expr) => {
36+
js_sys::Reflect::get(&raw_event, &JsValue::from_str($field))
37+
.expect("failed retrieving field from raw event")
38+
.dyn_into()
39+
.expect("failed casting field of raw event to proper type")
40+
};
41+
}
42+
macro_rules! get_optint {
43+
($field:expr) => {
44+
js_sys::Reflect::get(&raw_event, &JsValue::from_str($field))
45+
.ok()
46+
.map(|evt| {
47+
let float = evt.as_f64()
48+
.expect("failed casting field of raw event to proper type");
49+
let int = float as usize;
50+
assert!((int as f64 - float).abs() < 0.1, "received index that is not an integer: {}", float);
51+
int
52+
})
53+
};
54+
}
55+
Event {
56+
to: get!("to"),
57+
from: get!("from"),
58+
item: get!("item"),
59+
clone: get!("clone"),
60+
old_index: get_optint!("oldIndex"),
61+
new_index: get_optint!("newIndex"),
62+
old_draggable_index: get_optint!("oldDraggableIndex"),
63+
new_draggable_index: get_optint!("newDraggableIndex"),
64+
raw_event,
65+
}
66+
}
67+
}
68+
69+
#[repr(usize)]
70+
enum CallbackId {
71+
Choose,
72+
Unchoose,
73+
Start,
74+
End,
75+
Add,
76+
Update,
77+
Sort,
78+
Remove,
79+
Filter,
80+
Clone,
81+
Change,
82+
_Total,
83+
}
84+
1585
/// See https://github.com/SortableJS/Sortable for more documentation about available options
16-
pub struct Options(js_sys::Object);
86+
pub struct Options {
87+
options: js_sys::Object,
88+
callbacks: [Option<Rc<Closure<dyn FnMut(js_sys::Object)>>>; CallbackId::_Total as usize],
89+
}
1790

1891
macro_rules! option {
1992
( $setter:ident, $jsname:expr, $typ:ty, $builder:ident ) => {
20-
pub fn $setter(&self, value: $typ) -> &Options {
93+
pub fn $setter(&mut self, value: $typ) -> &mut Options {
2194
let res = js_sys::Reflect::set(
22-
&self.0,
95+
&self.options,
2396
&JsValue::from_str($jsname),
24-
&JsValue::$builder(value)
25-
).expect("setting property on object failed");
97+
&JsValue::$builder(value),
98+
)
99+
.expect("setting property on object failed");
26100
assert!(res, "failed setting property on object");
27101
self
28102
}
29-
}
103+
};
104+
}
105+
106+
macro_rules! callback {
107+
( $setter:ident, $jsname:expr, $id:ident ) => {
108+
pub fn $setter(&mut self, mut cb: impl 'static + FnMut(Event)) -> &Options {
109+
let cb = Closure::new(move |e: js_sys::Object| cb(Event::from_raw_event(e)));
110+
let res = js_sys::Reflect::set(&self.options, &JsValue::from_str($jsname), cb.as_ref())
111+
.expect("setting callback on object failed");
112+
assert!(res, "failed setting callback on object");
113+
self.callbacks[CallbackId::$id as usize] = Some(Rc::new(cb));
114+
self
115+
}
116+
};
30117
}
31118

32119
impl Options {
33120
pub fn new() -> Options {
34-
Options(js_sys::Object::new())
121+
Options {
122+
options: js_sys::Object::new(),
123+
callbacks: std::array::from_fn(|_| None),
124+
}
35125
}
36126

37127
option!(group, "group", &str, from_str);
@@ -56,7 +146,12 @@ impl Options {
56146

57147
option!(swap_threshold, "swapThreshold", f64, from_f64);
58148
option!(invert_swap, "invertSwap", bool, from_bool);
59-
option!(inverted_swap_threshold, "invertedSwapThreshold", f64, from_f64);
149+
option!(
150+
inverted_swap_threshold,
151+
"invertedSwapThreshold",
152+
f64,
153+
from_f64
154+
);
60155
option!(direction, "direction", &str, from_str);
61156

62157
option!(force_fallback, "forceFallback", bool, from_bool);
@@ -67,7 +162,24 @@ impl Options {
67162

68163
option!(dragover_bubble, "dragoverBubble", bool, from_bool);
69164
option!(remove_clone_on_hide, "removeCloneOnHide", bool, from_bool);
70-
option!(empty_insert_threshold, "emptyInsertThreshold", f64, from_f64);
165+
option!(
166+
empty_insert_threshold,
167+
"emptyInsertThreshold",
168+
f64,
169+
from_f64
170+
);
171+
172+
callback!(on_choose, "onChoose", Choose);
173+
callback!(on_unchoose, "onUnchoose", Unchoose);
174+
callback!(on_start, "onStart", Start);
175+
callback!(on_end, "onEnd", End);
176+
callback!(on_add, "onAdd", Add);
177+
callback!(on_update, "onUpdate", Update);
178+
callback!(on_sort, "onSort", Sort);
179+
callback!(on_remove, "onRemove", Remove);
180+
callback!(on_filter, "onFilter", Filter);
181+
callback!(on_clone, "onClone", Clone);
182+
callback!(on_change, "onChange", Change);
71183

72184
// TODO: all the callbacks
73185

@@ -76,10 +188,21 @@ impl Options {
76188
/// Note that you can set options on this object through `js_sys::Reflect`.
77189
/// This allows setting options that are not planned for by `sortable-js-rs`.
78190
pub fn options(&self) -> &js_sys::Object {
79-
&self.0
191+
&self.options
80192
}
81193

82-
pub fn apply(&self, elt: &web_sys::Element) {
83-
js::Sortable::new(elt, &self.0);
194+
pub fn apply(&self, elt: &web_sys::Element) -> Sortable {
195+
js::Sortable::new(elt, &self.options);
196+
Sortable {
197+
_callbacks: self.callbacks.clone(),
198+
}
84199
}
85200
}
201+
202+
/// Data related to the Sortable instance
203+
///
204+
/// It must be kept alive on the rust sideas long as the instance can call callbacks, as otherwise the link between the js-side callback and the rust-side callback would be lost.
205+
pub struct Sortable {
206+
/// Keep the callbacks alive
207+
_callbacks: [Option<Rc<Closure<dyn FnMut(js_sys::Object)>>>; CallbackId::_Total as usize],
208+
}

0 commit comments

Comments
 (0)