diff --git a/macros/src/html.rs b/macros/src/html.rs index a1db35d..cef7ce6 100644 --- a/macros/src/html.rs +++ b/macros/src/html.rs @@ -262,7 +262,7 @@ impl Element { let key = TokenTree::Ident(key.clone()); let value = process_value(value); body.extend(quote!( - element.events.#key = Some(typed_html::events::IntoEventHandler::into_event_handler(#value)); + element.events.#key = Some(#value.into()); )); } diff --git a/typed-html/src/events.rs b/typed-html/src/events.rs index aa1ac4f..c4d1dcf 100644 --- a/typed-html/src/events.rs +++ b/typed-html/src/events.rs @@ -3,6 +3,7 @@ use crate::OutputType; use htmlescape::encode_attribute; use std::fmt::{Display, Error, Formatter}; +use std::iter; /// Trait for event handlers. pub trait EventHandler { @@ -23,58 +24,57 @@ pub trait EventHandler { fn render(&self) -> Option; } -/// Trait for building event handlers from other types. -pub trait IntoEventHandler { - /// Construct an event handler from an instance of the source type. - fn into_event_handler(self) -> Box>; -} - -/// An uninhabited event type for string handlers. -pub enum StringEvent {} - -impl EventHandler for &'static str { - fn attach(&mut self, _target: &mut ::EventTarget) { - panic!("Silly wabbit, strings as event handlers are only for printing."); - } - - fn render(&self) -> Option { - Some(self.to_string()) - } -} - -impl IntoEventHandler for &'static str { - fn into_event_handler(self) -> Box> { - Box::new(self) - } -} - -impl EventHandler for String { - fn attach(&mut self, _target: &mut ::EventTarget) { - panic!("Silly wabbit, strings as event handlers are only for printing."); - } - - fn render(&self) -> Option { - Some(self.clone()) - } -} - -impl IntoEventHandler for String { - fn into_event_handler(self) -> Box> { - Box::new(self) - } -} - -macro_rules! declare_string_events { +macro_rules! declare_events_struct { ($($name:ident,)*) => { - pub struct StringEvents { + pub struct Events { $( - pub $name: Option>>, + pub $name: Option, )* } - impl Default for StringEvents { + impl Events { + pub fn iter(&self) -> impl Iterator { + iter::empty() + $( + .chain( + self.$name.iter() + .map(|value| (stringify!($name), value)) + ) + )* + } + + pub fn iter_mut(&mut self) -> impl Iterator { + iter::empty() + $( + .chain( + self.$name.iter_mut() + .map(|value| (stringify!($name), value)) + ) + )* + } + } + + impl IntoIterator for Events { + type Item = (&'static str, T); + type IntoIter = Box>; + + fn into_iter(mut self) -> Self::IntoIter { + Box::new( + iter::empty() + $( + .chain( + iter::once(self.$name.take()) + .filter(|value| value.is_some()) + .map(|value| (stringify!($name), value.unwrap())) + ) + )* + ) + } + } + + impl Default for Events { fn default() -> Self { - StringEvents { + Events { $( $name: None, )* @@ -82,12 +82,12 @@ macro_rules! declare_string_events { } } - impl Display for StringEvents { + impl Display for Events { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { $( if let Some(ref value) = self.$name { - write!(f, " on{}=\"{}\"", stringify!($name), - encode_attribute(value.render().unwrap().as_str()))?; + let attribute = encode_attribute(&value.to_string()); + write!(f, " on{}=\"{}\"", stringify!($name), attribute)?; } )* Ok(()) @@ -96,7 +96,7 @@ macro_rules! declare_string_events { } } -declare_string_events! { +declare_events_struct! { abort, autocomplete, autocompleteerror, @@ -161,3 +161,52 @@ declare_string_events! { volumechange, waiting, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_empty_events_iter() { + let events: Events<&str> = Events::default(); + + let mut iter = events.iter(); + assert_eq!(iter.next(), None); + } + + #[test] + fn test_events_iter() { + let mut events: Events<&str> = Events::default(); + events.abort = Some("abort"); + events.waiting = Some("waiting"); + + let mut iter = events.iter(); + assert_eq!(iter.next(), Some(("abort", &"abort"))); + assert_eq!(iter.next(), Some(("waiting", &"waiting"))); + assert_eq!(iter.next(), None); + } + + #[test] + fn test_events_iter_mut() { + let mut events: Events<&str> = Events::default(); + events.abort = Some("abort"); + events.waiting = Some("waiting"); + + let mut iter = events.iter_mut(); + assert_eq!(iter.next(), Some(("abort", &mut "abort"))); + assert_eq!(iter.next(), Some(("waiting", &mut "waiting"))); + assert_eq!(iter.next(), None); + } + + #[test] + fn test_events_into_iter() { + let mut events: Events<&str> = Events::default(); + events.abort = Some("abort"); + events.waiting = Some("waiting"); + + let mut iter = events.into_iter(); + assert_eq!(iter.next(), Some(("abort", "abort"))); + assert_eq!(iter.next(), Some(("waiting", "waiting"))); + assert_eq!(iter.next(), None); + } +} diff --git a/typed-html/src/lib.rs b/typed-html/src/lib.rs index 40b4dc0..0c868ee 100644 --- a/typed-html/src/lib.rs +++ b/typed-html/src/lib.rs @@ -1,3 +1,4 @@ +#![recursion_limit = "128"] //! This crate provides the `html!` macro for building HTML documents inside your //! Rust code using roughly [JSX] compatible syntax. //! @@ -221,7 +222,7 @@ pub trait OutputType { /// String output impl OutputType for String { - type Events = events::StringEvents; + type Events = events::Events; type EventTarget = (); type EventListenerHandle = (); } diff --git a/typed-html/src/output/stdweb.rs b/typed-html/src/output/stdweb.rs index dc37ed8..ec77c7a 100644 --- a/typed-html/src/output/stdweb.rs +++ b/typed-html/src/output/stdweb.rs @@ -6,7 +6,7 @@ use stdweb::web::{self, Element, EventListenerHandle, IElement, IEventTarget, IN use crate::OutputType; use crate::dom::VNode; -use crate::events::{EventHandler, IntoEventHandler}; +use crate::events::EventHandler; /// DOM output using the stdweb crate pub struct Stdweb; @@ -136,23 +136,13 @@ where } } -impl IntoEventHandler for F +impl From for Box> where F: FnMut(E) + 'static, E: ConcreteEvent + 'static, { - fn into_event_handler(self) -> Box> { - Box::new(EFn::new(self)) - } -} - -impl IntoEventHandler for EFn -where - F: FnMut(E) + 'static, - E: ConcreteEvent + 'static, -{ - fn into_event_handler(self) -> Box> { - Box::new(self) + fn from(f: F) -> Self { + Box::new(EFn::new(f)) } }