Skip to content

Commit

Permalink
feat: make callback use FnMut to accomodate stateful component requir…
Browse files Browse the repository at this point in the history
…ement to mutate the environment of a closure function
  • Loading branch information
ivanceras committed Apr 14, 2024
1 parent 52fe114 commit 8dd5531
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 25 deletions.
10 changes: 4 additions & 6 deletions crates/core/src/dom/component/stateful_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{any::TypeId, cell::RefCell, fmt, rc::Rc};

use crate::{
dom::{
events::on_mount, program::MountProcedure, Application, Cmd, Component, DomAttrValue,
events::on_component_mount, program::MountProcedure, Application, Cmd, Component, DomAttrValue,
DomNode, Program,
},
vdom::{Attribute, AttributeName, Leaf, Node},
Expand Down Expand Up @@ -139,21 +139,19 @@ pub fn stateful_component<COMP, MSG, MSG2>(
) -> Node<MSG>
where
COMP: Component<MSG = MSG2, XMSG = ()> + StatefulComponent + Application<MSG = MSG2> + 'static,
MSG: Default + 'static,
MSG: 'static,
MSG2: 'static,
{
let type_id = TypeId::of::<COMP>();
let attrs = attrs.into_iter().collect::<Vec<_>>();

let app = Rc::new(RefCell::new(app));

let program = Program::from_rc_app(Rc::clone(&app));
let mut program = Program::from_rc_app(Rc::clone(&app));
let children: Vec<Node<MSG>> = children.into_iter().collect();
let mount_event = on_mount(move |me| {
let mut program = program.clone();
let mount_event = on_component_mount(move |me| {
log::info!("stateful component is now mounted...");
program.mount(&me.target_node.as_node(), MountProcedure::append());
MSG::default()
});
Node::Leaf(Leaf::StatefulComponent(StatefulModel {
comp: app,
Expand Down
1 change: 1 addition & 0 deletions crates/core/src/dom/component/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ fn convert_attr_value_except_listener<MSG>(
AttributeValue::Simple(v) => Some(DomAttrValue::Simple(v.clone())),
AttributeValue::Style(v) => Some(DomAttrValue::Style(v.clone())),
AttributeValue::EventListener(_v) => None,
AttributeValue::ComponentEventListener(_v) => None,
AttributeValue::Empty => None,
}
}
Expand Down
16 changes: 16 additions & 0 deletions crates/core/src/dom/dom_patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use indexmap::IndexMap;
use std::rc::Rc;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsValue;
use crate::vdom::ComponentEventCallback;

/// a Patch where the virtual nodes are all created in the document.
/// This is necessary since the created Node doesn't contain references
Expand Down Expand Up @@ -99,6 +100,9 @@ where
AttributeValue::EventListener(v) => {
Some(DomAttrValue::EventListener(self.convert_event_listener(v)))
}
AttributeValue::ComponentEventListener(v) => {
Some(DomAttrValue::EventListener(self.convert_component_event_listener(v)))
}
AttributeValue::Empty => None,
}
}
Expand All @@ -117,6 +121,18 @@ where
});
closure
}

fn convert_component_event_listener(
&self,
component_callback: &ComponentEventCallback,
) -> Closure<dyn FnMut(web_sys::Event)> {
let component_callback = component_callback.clone();
let closure: Closure<dyn FnMut(web_sys::Event)> =
Closure::new(move |event: web_sys::Event| {
component_callback.emit(dom::Event::from(event));
});
closure
}
/// get the real DOM target node and make a DomPatch object for each of the Patch
pub(crate) fn convert_patches(
&self,
Expand Down
41 changes: 32 additions & 9 deletions crates/core/src/dom/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use web_sys::{
EventTarget, HtmlDetailsElement, HtmlElement, HtmlInputElement, HtmlSelectElement,
HtmlTextAreaElement,
};
use crate::vdom::ComponentEventCallback;

#[derive(Clone, Copy)]
#[repr(i16)]
Expand Down Expand Up @@ -83,7 +84,7 @@ impl From<web_sys::MouseEvent> for Event {
/// an event builder
pub fn on<F, MSG>(event_name: &'static str, f: F) -> Attribute<MSG>
where
F: Fn(Event) -> MSG + 'static,
F: FnMut(Event) -> MSG + 'static,
MSG: 'static,
{
vdom::attr(
Expand All @@ -93,18 +94,18 @@ where
}

/// on click event
pub fn on_click<F, MSG>(f: F) -> Attribute<MSG>
pub fn on_click<F, MSG>(mut f: F) -> Attribute<MSG>
where
F: Fn(MouseEvent) -> MSG + 'static,
F: FnMut(MouseEvent) -> MSG + 'static,
MSG: 'static,
{
on("click", move |event: Event| f(to_mouse_event(event)))
}

/// attach callback to the scroll event
pub fn on_scroll<F, MSG>(f: F) -> Attribute<MSG>
pub fn on_scroll<F, MSG>(mut f: F) -> Attribute<MSG>
where
F: Fn((i32, i32)) -> MSG + 'static,
F: FnMut((i32, i32)) -> MSG + 'static,
MSG: 'static,
{
on("scroll", move |event: Event| {
Expand Down Expand Up @@ -139,9 +140,9 @@ impl MountEvent {
}

/// custom mount event
pub fn on_mount<F, MSG>(f: F) -> Attribute<MSG>
pub fn on_mount<F, MSG>(mut f: F) -> Attribute<MSG>
where
F: Fn(MountEvent) -> MSG + 'static,
F: FnMut(MountEvent) -> MSG + 'static,
MSG: 'static,
{
on("mount", move |event: Event| {
Expand All @@ -155,6 +156,28 @@ where
})
}

/// custom mount event
pub fn on_component_mount<F, MSG>(mut f: F) -> Attribute<MSG>
where
F: FnMut(MountEvent) + 'static,
MSG: 'static,
{
let cb = move |event: Event| {
let web_event = event.as_web().expect("must be a web event");
let event_target = web_event.target().expect("must have a target");
let target_node: web_sys::Node = event_target.unchecked_into();
let me = MountEvent {
target_node: DomNode::from(target_node),
};
f(me);
};
vdom::attr(
"mount",
AttributeValue::ComponentEventListener(ComponentEventCallback::from(cb)),
)
}


macro_rules! declare_events {

( $(
Expand All @@ -167,8 +190,8 @@ macro_rules! declare_events {
concat!("attach an [",stringify!($name),"](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/",stringify!($name),") event to the html element"),
$(#[$attr])*
#[inline]
pub fn $name<CB, MSG>(cb: CB) -> crate::vdom::Attribute<MSG>
where CB: Fn($ret) -> MSG + 'static,
pub fn $name<CB, MSG>(mut cb: CB) -> crate::vdom::Attribute<MSG>
where CB: FnMut($ret) -> MSG + 'static,
MSG: 'static,
{
on(stringify!($event), move|event:Event|{
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/vdom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@ pub mod patch;
/// Callback where Event type is supplied
/// for Components
pub type EventCallback<MSG> = Callback<Event, MSG>;


/// Mount callback is used for mounting the component into the DOM
/// This requires no MSG to be emitted
pub type ComponentEventCallback = Callback<Event, ()>;
12 changes: 10 additions & 2 deletions crates/core/src/vdom/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

use derive_where::derive_where;
use indexmap::IndexMap;

use crate::vdom::ComponentEventCallback;
use crate::vdom::EventCallback;

pub use attribute_value::AttributeValue;
pub use callback::Callback;
pub use style::Style;
Expand Down Expand Up @@ -40,8 +41,10 @@ pub struct Attribute<MSG> {

/// The Attributes partition into 4 different types
pub struct GroupedAttributeValues<'a, MSG> {
/// the listeners of the event listeners
/// the event listeners
pub listeners: Vec<&'a EventCallback<MSG>>,
/// the component event listeners
pub component_callbacks: Vec<&'a ComponentEventCallback>,
/// plain attribute values
pub plain_values: Vec<&'a Value>,
/// style attribute values
Expand Down Expand Up @@ -101,6 +104,7 @@ impl<MSG> Attribute<MSG> {
/// grouped values into plain, function calls, styles and event listeners
pub(crate) fn group_values(attr: &Attribute<MSG>) -> GroupedAttributeValues<MSG> {
let mut listeners = vec![];
let mut component_callbacks = vec![];
let mut plain_values = vec![];
let mut styles = vec![];
for av in attr.value() {
Expand All @@ -114,11 +118,15 @@ impl<MSG> Attribute<MSG> {
AttributeValue::EventListener(cb) => {
listeners.push(cb);
}
AttributeValue::ComponentEventListener(cb) => {
component_callbacks.push(cb);
}
AttributeValue::Empty => (),
}
}
GroupedAttributeValues {
listeners,
component_callbacks,
plain_values,
styles,
}
Expand Down
4 changes: 3 additions & 1 deletion crates/core/src/vdom/attribute/attribute_value.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{html::attributes::Style, vdom::EventCallback, vdom::Value};

use crate::vdom::ComponentEventCallback;
use derive_where::derive_where;

/// Values of an attribute can be in these variants
Expand All @@ -11,6 +11,8 @@ pub enum AttributeValue<MSG> {
Style(Vec<Style>),
/// Event EventCallback
EventListener(EventCallback<MSG>),
/// Component Event Listener
ComponentEventListener(ComponentEventCallback),
/// no value
Empty,
}
Expand Down
12 changes: 6 additions & 6 deletions crates/core/src/vdom/attribute/callback.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Callbacks contains function that can be called at a later time.
//! This is used in containing an event listener attached to an DOM element.
use std::{any::TypeId, fmt, rc::Rc};
use std::{any::TypeId, fmt, rc::Rc, cell::RefCell};

/// A generic sized representation of a function that can be
/// attached to a Node. The callback will essentially be owned by the element
Expand All @@ -25,7 +25,7 @@ use std::{any::TypeId, fmt, rc::Rc};
///
pub struct Callback<IN, OUT> {
/// the function to be executed
func: Rc<dyn Fn(IN) -> OUT>,
func: Rc<RefCell<dyn FnMut(IN) -> OUT>>,
/// the type_id of the function
func_type_id: TypeId,
/// the type type_id of the event this callback will be attached to
Expand All @@ -36,13 +36,13 @@ pub struct Callback<IN, OUT> {

impl<IN, F, OUT> From<F> for Callback<IN, OUT>
where
F: Fn(IN) -> OUT + 'static,
F: FnMut(IN) -> OUT + 'static,
OUT: 'static,
IN: 'static,
{
fn from(func: F) -> Self {
Self {
func: Rc::new(func),
func: Rc::new(RefCell::new(func)),
func_type_id: TypeId::of::<F>(),
event_type_id: TypeId::of::<IN>(),
msg_type_id: TypeId::of::<OUT>(),
Expand Down Expand Up @@ -72,7 +72,7 @@ where
{
/// This method calls the actual callback.
pub fn emit(&self, input: IN) -> OUT {
(self.func)(input)
(self.func.borrow_mut())(input)
}

/// map this Callback msg such that `Callback<IN, OUT>` becomes `Callback<IN, MSG2>`
Expand All @@ -88,7 +88,7 @@ where
cb2(out)
};
Callback {
func: Rc::new(cb),
func: Rc::new(RefCell::new(cb)),
func_type_id: source_func_type_id,
event_type_id: TypeId::of::<IN>(),
msg_type_id: TypeId::of::<OUT>(),
Expand Down
1 change: 1 addition & 0 deletions crates/core/src/vdom/map_msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ impl<MSG> AttributeValue<MSG> {
AttributeValue::Simple(this) => AttributeValue::Simple(this),
AttributeValue::Style(this) => AttributeValue::Style(this),
AttributeValue::EventListener(this) => AttributeValue::EventListener(this.map_msg(cb)),
AttributeValue::ComponentEventListener(this) => AttributeValue::ComponentEventListener(this),
AttributeValue::Empty => AttributeValue::Empty,
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/core/src/vdom/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,9 @@ impl<MSG> Attribute<MSG> {
/// render attributes
fn render(&self, buffer: &mut dyn fmt::Write) -> fmt::Result {
let GroupedAttributeValues {
listeners: _,
plain_values,
styles,
..
} = Attribute::group_values(self);

// These are attribute values which specifies the state of the element
Expand Down

0 comments on commit 8dd5531

Please sign in to comment.