Make it generic on event type, determined by output type.

Unfortunately, this means the macro now requires a type annotation when 
using event handlers.

Closes #6, #17.
This commit is contained in:
Bodil Stokke 2018-12-01 18:19:23 +00:00
parent 77a3c6d840
commit 818c8ebdac
24 changed files with 441 additions and 376 deletions

View File

@ -2,7 +2,7 @@
members = [ members = [
"typed-html", "typed-html",
"macros", "macros",
"examples/wasm", "examples/stdweb",
"examples/rocket", "examples/rocket",
"ui", "ui",
] ]

View File

@ -9,8 +9,8 @@ use rocket::http::{ContentType, Status};
use rocket::response::{Responder, Result}; use rocket::response::{Responder, Result};
use rocket::{get, routes, Request, Response}; use rocket::{get, routes, Request, Response};
use std::io::Cursor; use std::io::Cursor;
use typed_html::types::LinkType;
use typed_html::elements::FlowContent; use typed_html::elements::FlowContent;
use typed_html::types::LinkType;
use typed_html::{dom::DOMTree, html, text, OutputType}; use typed_html::{dom::DOMTree, html, text, OutputType};
struct Html(DOMTree<String>); struct Html(DOMTree<String>);
@ -64,7 +64,7 @@ fn index() -> Html {
<p>"<img src=\"javascript:alert('pwned lol')\">"</p> <p>"<img src=\"javascript:alert('pwned lol')\">"</p>
<button disabled=a onclick="alert('She is not a cat.')">"Click me!"</button> <button disabled=a onclick="alert('She is not a cat.')">"Click me!"</button>
</div> </div>
))) : String)))
} }
fn main() { fn main() {

View File

@ -0,0 +1,8 @@
[package]
name = "typed-html-stdweb-test"
version = "0.1.0"
authors = ["Bodil Stokke <bodil@bodil.org>"]
[dependencies]
typed-html = { path = "../../typed-html", features = ["stdweb"] }
stdweb = "0.4.10"

View File

@ -0,0 +1,31 @@
#![recursion_limit = "256"]
extern crate stdweb;
extern crate typed_html;
use stdweb::web::{self, INode};
use typed_html::dom::Node;
use typed_html::html;
use typed_html::output::stdweb::Stdweb;
fn main() {
let mut doc = html!(
<div>
<h1>"Hello Kitty"</h1>
<p>
"She is not a "<em><a href="https://en.wikipedia.org/wiki/Cat">"cat"</a></em>
". She is a "<em>"human girl"</em>"."
</p>
<p>
<button onclick={ |_event| web::alert("Hello Joe!") }>
"Call Joe"
</button>
</p>
</div>
: Stdweb);
let vdom = doc.vnode();
let document = web::document();
let body = document.body().expect("no body element in doc");
let tree = Stdweb::build(&document, vdom).unwrap();
body.append_child(&tree);
}

View File

@ -1,9 +0,0 @@
[package]
name = "typed-html-wasm-test"
version = "0.1.0"
authors = ["Bodil Stokke <bodil@bodil.org>"]
[dependencies]
typed-html-macros = { path = "../../macros" }
typed-html = { path = "../../typed-html" }
stdweb = "0.4.10"

View File

@ -1,59 +0,0 @@
#![recursion_limit = "256"]
extern crate stdweb;
extern crate typed_html;
extern crate typed_html_macros;
use stdweb::web::{self, Element, IElement, INode};
use typed_html::dom::{Node, VNode};
use typed_html::events::Events;
use typed_html::{for_events, html, DOM};
fn install_handlers(target: &Element, handlers: &mut Events<DOM>) {
for_events!(handler in handlers => {
handler.attach(target);
});
}
fn build(
document: &web::Document,
vnode: VNode<'_, DOM>,
) -> Result<web::Node, web::error::InvalidCharacterError> {
match vnode {
VNode::Text(text) => Ok(document.create_text_node(&text).into()),
VNode::Element(element) => {
let mut node = document.create_element(element.name)?;
for (key, value) in element.attributes {
node.set_attribute(&key, &value)?;
}
install_handlers(&node, element.events);
for child in element.children {
let child_node = build(document, child)?;
node.append_child(&child_node);
}
Ok(node.into())
}
}
}
fn main() {
let mut doc = html!(
<div>
<h1>"Hello Kitty"</h1>
<p>
"She is not a "<em><a href="https://en.wikipedia.org/wiki/Cat">"cat"</a></em>
". She is a "<em>"human girl"</em>"."
</p>
<p>
<button onclick=(|_event| web::alert("Hello Joe!"))>
"Call Joe"
</button>
</p>
</div>
);
let vdom = doc.vnode();
let document = web::document();
let body = document.body().expect("no body element in doc");
let tree = build(&document, vdom).unwrap();
body.append_child(&tree);
}

View File

@ -55,70 +55,3 @@ pub static SELF_CLOSING: &[&str] = &[
"track", "track",
"wbr", "wbr",
]; ];
// This NEEDS to be a sorted list!
pub static ATTR_EVENTS: &[&str] = &[
"abort",
// "autocomplete",
// "autocompleteerror",
"blur",
// "cancel",
// "canplay",
// "canplaythrough",
"change",
"click",
// "close",
"contextmenu",
// "cuechange",
"dblclick",
"drag",
"dragend",
"dragenter",
"dragexit",
"dragleave",
"dragover",
"dragstart",
"drop",
// "durationchange",
// "emptied",
// "ended",
"error",
"focus",
"input",
// "invalid",
"keydown",
"keypress",
"keyup",
"load",
// "loadeddata",
// "loadedmetadata",
"loadstart",
"mousedown",
"mouseenter",
"mouseleave",
"mousemove",
"mouseout",
"mouseover",
"mouseup",
"mousewheel",
// "pause",
// "play",
// "playing",
"progress",
// "ratechange",
// "reset",
"resize",
"scroll",
// "seeked",
// "seeking",
// "select",
// "show",
// "sort",
// "stalled",
"submit",
// "suspend",
// "timeupdate",
// "toggle",
// "volumechange",
// "waiting",
];

View File

@ -1,7 +1,7 @@
use proc_macro2::{Ident, Literal, Span, TokenStream, TokenTree}; use proc_macro2::{Ident, Literal, TokenStream, TokenTree};
use quote::quote; use quote::quote;
use config::{global_attrs, ATTR_EVENTS, SELF_CLOSING}; use config::{global_attrs, SELF_CLOSING};
use error::ParseError; use error::ParseError;
use ident; use ident;
use lexer::{Lexer, Token}; use lexer::{Lexer, Token};
@ -104,10 +104,9 @@ impl Declare {
quote!( quote!(
pub struct #elem_name<T> where T: ::OutputType { pub struct #elem_name<T> where T: ::OutputType {
phantom_output: std::marker::PhantomData<T>,
pub attrs: #attr_type_name, pub attrs: #attr_type_name,
pub data_attributes: Vec<(&'static str, String)>, pub data_attributes: Vec<(&'static str, String)>,
pub events: ::events::Events<T>, pub events: T::Events,
#body #body
} }
) )
@ -144,8 +143,7 @@ impl Declare {
impl<T> #elem_name<T> where T: ::OutputType { impl<T> #elem_name<T> where T: ::OutputType {
pub fn new(#args) -> Self { pub fn new(#args) -> Self {
#elem_name { #elem_name {
phantom_output: std::marker::PhantomData, events: T::Events::default(),
events: ::events::Events::default(),
#body #body
} }
} }
@ -307,13 +305,11 @@ impl Declare {
let print_children = if self.req_children.is_empty() { let print_children = if self.req_children.is_empty() {
if self.opt_children.is_some() { if self.opt_children.is_some() {
if !SELF_CLOSING.contains(&elem_name.to_string().as_str()) { if !SELF_CLOSING.contains(&elem_name.to_string().as_str()) {
quote!(if self.children.is_empty() { quote!(
write!(f, "></{}>", #name)
} else {
write!(f, ">")?; write!(f, ">")?;
#print_opt_children #print_opt_children
write!(f, "</{}>", #name) write!(f, "</{}>", #name)
}) )
} else { } else {
quote!(if self.children.is_empty() { quote!(if self.children.is_empty() {
write!(f, " />") write!(f, " />")
@ -323,12 +319,10 @@ impl Declare {
write!(f, "</{}>", #name) write!(f, "</{}>", #name)
}) })
} }
} else if !SELF_CLOSING.contains(&elem_name.to_string().as_str()) {
quote!(write!(f, "></{}>", #name))
} else { } else {
if !SELF_CLOSING.contains(&elem_name.to_string().as_str()) { quote!(write!(f, "/>"))
quote!(write!(f, "></{}>", #name))
} else {
quote!(write!(f, "/>"))
}
} }
} else { } else {
quote!( quote!(
@ -349,20 +343,11 @@ impl Declare {
)); ));
} }
let mut print_events = TokenStream::new();
for event in ATTR_EVENTS {
let event_name = TokenTree::Ident(Ident::new(event, Span::call_site()));
let event_str = TokenTree::Literal(Literal::string(event));
print_events.extend(quote!(
if let Some(ref value) = self.events.#event_name {
write!(f, " on{}=\"{}\"", #event_str,
::htmlescape::encode_attribute(value.render().unwrap().as_str()))?;
}
));
}
quote!( quote!(
impl<T> std::fmt::Display for #elem_name<T> where T: ::OutputType { impl<T> std::fmt::Display for #elem_name<T>
where
T: ::OutputType,
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "<{}", #name)?; write!(f, "<{}", #name)?;
#print_attrs #print_attrs
@ -370,7 +355,7 @@ impl Declare {
write!(f, " data-{}=\"{}\"", key, write!(f, " data-{}=\"{}\"", key,
::htmlescape::encode_attribute(&value))?; ::htmlescape::encode_attribute(&value))?;
} }
#print_events write!(f, "{}", self.events)?;
#print_children #print_children
} }
} }

View File

@ -160,12 +160,19 @@ CodeBlock: Group = BraceGroupToken => match <> {
_ => unreachable!() _ => unreachable!()
}; };
pub Node: Node = { Node: Node = {
Element => Node::Element(<>), Element => Node::Element(<>),
TextNode => Node::Text(<>), TextNode => Node::Text(<>),
CodeBlock => Node::Block(<>), CodeBlock => Node::Block(<>),
}; };
pub NodeWithType: (Node, Option<Vec<Token>>) = {
Node => (<>, None),
<Node> ":" <TypeSpec> => {
let (node, spec) = (<>);
(node, Some(spec))
},
};
// The declare macro // The declare macro

View File

@ -1,10 +1,10 @@
use proc_macro2::{Delimiter, Group, Ident, Literal, TokenStream, TokenTree}; use proc_macro2::{Delimiter, Group, Ident, Literal, TokenStream, TokenTree};
use quote::{quote, quote_spanned}; use quote::{quote, quote_spanned};
use config::{required_children, ATTR_EVENTS}; use config::required_children;
use error::ParseError; use error::ParseError;
use ident; use ident;
use lexer::{Lexer, Token}; use lexer::{to_stream, Lexer, Token};
use map::StringyMap; use map::StringyMap;
use parser::grammar; use parser::grammar;
@ -18,33 +18,34 @@ pub enum Node {
} }
impl Node { impl Node {
pub fn into_token_stream(self) -> Result<TokenStream, TokenStream> { pub fn into_token_stream(self, ty: &Option<Vec<Token>>) -> Result<TokenStream, TokenStream> {
match self { match self {
Node::Element(el) => el.into_token_stream(), Node::Element(el) => el.into_token_stream(ty),
Node::Text(text) => { Node::Text(text) => {
let text = TokenTree::Literal(text); let text = TokenTree::Literal(text);
Ok(quote!(Box::new(typed_html::dom::TextNode::new(#text.to_string())))) Ok(quote!(Box::new(typed_html::dom::TextNode::new(#text.to_string()))))
} }
Node::Block(group) => { Node::Block(group) => {
let span = group.span(); let span = group.span();
let error = "you cannot use a block as a top level element or a required child element"; let error =
Err(quote_spanned!{ span=> "you cannot use a block as a top level element or a required child element";
Err(quote_spanned! { span=>
compile_error! { #error } compile_error! { #error }
}) })
} }
} }
} }
fn into_child_stream(self) -> Result<TokenStream, TokenStream> { fn into_child_stream(self, ty: &Option<Vec<Token>>) -> Result<TokenStream, TokenStream> {
match self { match self {
Node::Element(el) => { Node::Element(el) => {
let el = el.into_token_stream()?; let el = el.into_token_stream(ty)?;
Ok(quote!( Ok(quote!(
element.children.push(#el); element.children.push(#el);
)) ))
} }
tx @ Node::Text(_) => { tx @ Node::Text(_) => {
let tx = tx.into_token_stream()?; let tx = tx.into_token_stream(ty)?;
Ok(quote!( Ok(quote!(
element.children.push(#tx); element.children.push(#tx);
)) ))
@ -92,10 +93,8 @@ fn extract_event_handlers(
let prefix = "on"; let prefix = "on";
if key_name.starts_with(prefix) { if key_name.starts_with(prefix) {
let event_name = &key_name[prefix.len()..]; let event_name = &key_name[prefix.len()..];
if ATTR_EVENTS.binary_search(&event_name).is_ok() { let value = attrs.remove(&key).unwrap();
let value = attrs.remove(&key).unwrap(); events.insert(ident::new_raw(event_name, key.span()), value);
events.insert(ident::new_raw(event_name, key.span()), value);
}
} }
} }
events events
@ -121,7 +120,7 @@ fn is_string_literal(literal: &Literal) -> bool {
} }
impl Element { impl Element {
fn into_token_stream(mut self) -> Result<TokenStream, TokenStream> { fn into_token_stream(mut self, ty: &Option<Vec<Token>>) -> Result<TokenStream, TokenStream> {
let name = self.name; let name = self.name;
let name_str = name.to_string(); let name_str = name.to_string();
let typename: TokenTree = Ident::new(&name_str, name.span()).into(); let typename: TokenTree = Ident::new(&name_str, name.span()).into();
@ -151,12 +150,12 @@ impl Element {
.children .children
.split_off(req_names.len()) .split_off(req_names.len())
.into_iter() .into_iter()
.map(Node::into_child_stream) .map(|node| node.into_child_stream(ty))
.collect::<Result<Vec<TokenStream>, TokenStream>>()?; .collect::<Result<Vec<TokenStream>, TokenStream>>()?;
let req_children = self let req_children = self
.children .children
.into_iter() .into_iter()
.map(Node::into_token_stream) .map(|node| node.into_token_stream(ty))
.collect::<Result<Vec<TokenStream>, TokenStream>>()?; .collect::<Result<Vec<TokenStream>, TokenStream>>()?;
let mut body = TokenStream::new(); let mut body = TokenStream::new();
@ -226,9 +225,15 @@ impl Element {
args.extend(quote!( #arg, )); args.extend(quote!( #arg, ));
} }
let mut type_annotation = TokenStream::new();
if let Some(ty) = ty {
let type_var = to_stream(ty.clone());
type_annotation.extend(quote!(: typed_html::elements::#typename<#type_var>));
}
Ok(quote!( Ok(quote!(
{ {
let mut element = typed_html::elements::#typename::new(#args); let mut element #type_annotation = typed_html::elements::#typename::new(#args);
#body #body
Box::new(element) Box::new(element)
} }
@ -237,6 +242,6 @@ impl Element {
} }
// FIXME report a decent error when the macro contains multiple top level elements // FIXME report a decent error when the macro contains multiple top level elements
pub fn expand_html(input: &[Token]) -> Result<Node, ParseError> { pub fn expand_html(input: &[Token]) -> Result<(Node, Option<Vec<Token>>), ParseError> {
grammar::NodeParser::new().parse(Lexer::new(input)) grammar::NodeWithTypeParser::new().parse(Lexer::new(input))
} }

View File

@ -32,7 +32,7 @@ pub fn html(input: TokenStream) -> TokenStream {
let result = html::expand_html(&stream); let result = html::expand_html(&stream);
TokenStream::from(match result { TokenStream::from(match result {
Err(err) => error::parse_error(&stream, &err), Err(err) => error::parse_error(&stream, &err),
Ok(node) => match node.into_token_stream() { Ok((node, ty)) => match node.into_token_stream(&ty) {
Err(err) => err, Err(err) => err,
Ok(success) => success, Ok(success) => success,
}, },

View File

@ -22,6 +22,6 @@ mime = "0.3.12"
language-tags = "0.2.2" language-tags = "0.2.2"
http = "0.1.13" http = "0.1.13"
htmlescape = "0.3.1" htmlescape = "0.3.1"
stdweb = "0.4.10"
proc-macro-hack = "0.5.2" proc-macro-hack = "0.5.2"
proc-macro-nested = "0.1.0" proc-macro-nested = "0.1.0"
stdweb = { version = "0.4.10", optional = true }

View File

@ -5,7 +5,6 @@ use std::marker::PhantomData;
use super::OutputType; use super::OutputType;
use elements::{FlowContent, PhrasingContent}; use elements::{FlowContent, PhrasingContent};
use events::Events;
use htmlescape::encode_minimal; use htmlescape::encode_minimal;
/// A boxed DOM tree, as returned from the `html!` macro. /// A boxed DOM tree, as returned from the `html!` macro.
@ -50,7 +49,7 @@ pub enum VNode<'a, T: OutputType + 'a> {
pub struct VElement<'a, T: OutputType + 'a> { pub struct VElement<'a, T: OutputType + 'a> {
pub name: &'static str, pub name: &'static str,
pub attributes: Vec<(&'static str, String)>, pub attributes: Vec<(&'static str, String)>,
pub events: &'a mut Events<T>, pub events: &'a mut T::Events,
pub children: Vec<VNode<'a, T>>, pub children: Vec<VNode<'a, T>>,
} }
@ -73,7 +72,10 @@ pub trait Node<T: OutputType>: Display {
fn vnode(&mut self) -> VNode<T>; fn vnode(&mut self) -> VNode<T>;
} }
impl<T> IntoIterator for Box<dyn Node<T>> where T: OutputType { impl<T> IntoIterator for Box<dyn Node<T>>
where
T: OutputType,
{
type Item = Box<dyn Node<T>>; type Item = Box<dyn Node<T>>;
type IntoIter = std::vec::IntoIter<Box<dyn Node<T>>>; type IntoIter = std::vec::IntoIter<Box<dyn Node<T>>>;

View File

@ -1,129 +1,8 @@
//! Event handlers. //! Event handlers.
use std::marker::PhantomData; use super::OutputType;
use stdweb::web::event::*; use htmlescape::encode_attribute;
use stdweb::web::{Element, EventListenerHandle, IEventTarget}; use std::fmt::{Display, Error, Formatter};
use super::{OutputType, DOM};
macro_rules! declare_events {
($($name:ident : $type:ty ,)*) => {
/// Container type for DOM events.
pub struct Events<T: OutputType> {
$(
pub $name: Option<Box<dyn EventHandler<T, $type>>>,
)*
}
impl<T: OutputType> Default for Events<T> {
fn default() -> Self {
Events {
$(
$name: None,
)*
}
}
}
/// Iterate over the defined events on a DOM object.
///
/// # Examples
///
/// ```
/// # use typed_html::{html, for_events};
/// # use typed_html::dom::{DOMTree, VNode};
/// # fn main() {
/// let mut doc: DOMTree<String> = html!(
/// <button onclick="alert('clicked!')"/>
/// );
/// if let VNode::Element(element) = doc.vnode() {
/// for_events!(event in element.events => {
/// assert_eq!("alert('clicked!')", event.render().unwrap());
/// });
/// }
/// # }
/// ```
#[macro_export]
macro_rules! for_events {
($event:ident in $events:expr => $body:block) => {
$(
if let Some(ref mut $event) = $events.$name $body
)*
}
}
}
}
// TODO? these are all the "on*" attributes defined in the HTML5 standard, with
// the ones I've been unable to match to stdweb event types commented out.
//
// This needs review.
declare_events! {
abort: ResourceAbortEvent,
// autocomplete: Event,
// autocompleteerror: Event,
blur: BlurEvent,
// cancel: Event,
// canplay: Event,
// canplaythrough: Event,
change: ChangeEvent,
click: ClickEvent,
// close: Event,
contextmenu: ContextMenuEvent,
// cuechange: Event,
dblclick: DoubleClickEvent,
drag: DragEvent,
dragend: DragEndEvent,
dragenter: DragEnterEvent,
dragexit: DragExitEvent,
dragleave: DragLeaveEvent,
dragover: DragOverEvent,
dragstart: DragStartEvent,
drop: DragDropEvent,
// durationchange: Event,
// emptied: Event,
// ended: Event,
error: ResourceErrorEvent,
focus: FocusEvent,
input: InputEvent,
// invalid: Event,
keydown: KeyDownEvent,
keypress: KeyPressEvent,
keyup: KeyUpEvent,
load: ResourceLoadEvent,
// loadeddata: Event,
// loadedmetadata: Event,
loadstart: LoadStartEvent,
mousedown: MouseDownEvent,
mouseenter: MouseEnterEvent,
mouseleave: MouseLeaveEvent,
mousemove: MouseMoveEvent,
mouseout: MouseOutEvent,
mouseover: MouseOverEvent,
mouseup: MouseUpEvent,
mousewheel: MouseWheelEvent,
// pause: Event,
// play: Event,
// playing: Event,
progress: ProgressEvent,
// ratechange: Event,
// reset: Event,
resize: ResizeEvent,
scroll: ScrollEvent,
// seeked: Event,
// seeking: Event,
// select: Event,
// show: Event,
// sort: Event,
// stalled: Event,
submit: SubmitEvent,
// suspend: Event,
// timeupdate: Event,
// toggle: Event,
// volumechange: Event,
// waiting: Event,
}
/// Trait for event handlers. /// Trait for event handlers.
pub trait EventHandler<T: OutputType, E> { pub trait EventHandler<T: OutputType, E> {
@ -134,7 +13,7 @@ pub trait EventHandler<T: OutputType, E> {
/// intended for server side rendering. /// intended for server side rendering.
// fn build(self) -> Option<Box<FnMut(EventType) + 'static>>; // fn build(self) -> Option<Box<FnMut(EventType) + 'static>>;
fn attach(&mut self, target: &Element) -> EventListenerHandle; fn attach(&mut self, target: &mut T::EventTarget) -> T::EventListenerHandle;
/// Render this event handler as a string. /// Render this event handler as a string.
/// ///
@ -150,56 +29,11 @@ pub trait IntoEventHandler<T: OutputType, E> {
fn into_event_handler(self) -> Box<dyn EventHandler<T, E>>; fn into_event_handler(self) -> Box<dyn EventHandler<T, E>>;
} }
/// Wrapper type for closures as event handlers. /// An uninhabited event type for string handlers.
pub struct EFn<F, E>(Option<F>, PhantomData<E>); pub enum StringEvent {}
impl<F, E> EFn<F, E> impl EventHandler<String, StringEvent> for &'static str {
where fn attach(&mut self, _target: &mut <String as OutputType>::EventTarget) {
F: FnMut(E) + 'static,
E: ConcreteEvent,
{
pub fn new(f: F) -> Self {
EFn(Some(f), PhantomData)
}
}
impl<F, E> IntoEventHandler<DOM, E> for EFn<F, E>
where
F: FnMut(E) + 'static,
E: ConcreteEvent + 'static,
{
fn into_event_handler(self) -> Box<dyn EventHandler<DOM, E>> {
Box::new(self)
}
}
impl<F, E> IntoEventHandler<DOM, E> for F
where
F: FnMut(E) + 'static,
E: ConcreteEvent + 'static,
{
fn into_event_handler(self) -> Box<dyn EventHandler<DOM, E>> {
Box::new(EFn::new(self))
}
}
impl<F, E> EventHandler<DOM, E> for EFn<F, E>
where
F: FnMut(E) + 'static,
E: ConcreteEvent,
{
fn attach(&mut self, target: &Element) -> EventListenerHandle {
let handler = self.0.take().unwrap();
target.add_event_listener(handler)
}
fn render(&self) -> Option<String> {
None
}
}
impl<E> EventHandler<String, E> for &'static str {
fn attach(&mut self, _target: &Element) -> EventListenerHandle {
panic!("Silly wabbit, strings as event handlers are only for printing."); panic!("Silly wabbit, strings as event handlers are only for printing.");
} }
@ -208,8 +42,122 @@ impl<E> EventHandler<String, E> for &'static str {
} }
} }
impl<E> IntoEventHandler<String, E> for &'static str { impl IntoEventHandler<String, StringEvent> for &'static str {
fn into_event_handler(self) -> Box<dyn EventHandler<String, E>> { fn into_event_handler(self) -> Box<dyn EventHandler<String, StringEvent>> {
Box::new(self) Box::new(self)
} }
} }
impl EventHandler<String, StringEvent> for String {
fn attach(&mut self, _target: &mut <String as OutputType>::EventTarget) {
panic!("Silly wabbit, strings as event handlers are only for printing.");
}
fn render(&self) -> Option<String> {
Some(self.clone())
}
}
impl IntoEventHandler<String, StringEvent> for String {
fn into_event_handler(self) -> Box<dyn EventHandler<String, StringEvent>> {
Box::new(self)
}
}
macro_rules! declare_string_events {
($($name:ident,)*) => {
pub struct StringEvents {
$(
pub $name: Option<Box<dyn EventHandler<String, StringEvent>>>,
)*
}
impl Default for StringEvents {
fn default() -> Self {
StringEvents {
$(
$name: None,
)*
}
}
}
impl Display for StringEvents {
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()))?;
}
)*
Ok(())
}
}
}
}
declare_string_events! {
abort,
autocomplete,
autocompleteerror,
blur,
cancel,
canplay,
canplaythrough,
change,
click,
close,
contextmenu,
cuechange,
dblclick,
drag,
dragend,
dragenter,
dragexit,
dragleave,
dragover,
dragstart,
drop,
durationchange,
emptied,
ended,
error,
focus,
input,
invalid,
keydown,
keypress,
keyup,
load,
loadeddata,
loadedmetadata,
loadstart,
mousedown,
mouseenter,
mouseleave,
mousemove,
mouseout,
mouseover,
mouseup,
mousewheel,
pause,
play,
playing,
progress,
ratechange,
reset,
resize,
scroll,
seeked,
seeking,
select,
show,
sort,
stalled,
submit,
suspend,
timeupdate,
toggle,
volumechange,
waiting,
}

View File

@ -5,7 +5,7 @@
//! //!
//! ``` //! ```
//! # #![recursion_limit = "128"] //! # #![recursion_limit = "128"]
//! # use typed_html::{html, for_events}; //! # use typed_html::html;
//! # use typed_html::dom::{DOMTree, VNode}; //! # use typed_html::dom::{DOMTree, VNode};
//! # use typed_html::types::Metadata; //! # use typed_html::types::Metadata;
//! # fn main() { //! # fn main() {
@ -200,11 +200,14 @@ extern crate language_tags;
extern crate mime; extern crate mime;
extern crate proc_macro_hack; extern crate proc_macro_hack;
extern crate proc_macro_nested; extern crate proc_macro_nested;
extern crate stdweb;
extern crate strum; extern crate strum;
extern crate typed_html_macros; extern crate typed_html_macros;
#[cfg(feature = "stdweb")]
extern crate stdweb;
use proc_macro_hack::proc_macro_hack; use proc_macro_hack::proc_macro_hack;
use std::fmt::Display;
#[proc_macro_hack(support_nested)] #[proc_macro_hack(support_nested)]
pub use typed_html_macros::html; pub use typed_html_macros::html;
@ -212,14 +215,22 @@ pub use typed_html_macros::html;
pub mod dom; pub mod dom;
pub mod elements; pub mod elements;
pub mod events; pub mod events;
pub mod output;
pub mod types; pub mod types;
/// Marker trait for outputs /// Marker trait for outputs
pub trait OutputType {} pub trait OutputType {
/// The type that contains events for this output.
type Events: Default + Display;
/// The type of event targets for this output.
type EventTarget;
/// The type that's returned from attaching an event listener to a target.
type EventListenerHandle;
}
/// String output /// String output
impl OutputType for String {} impl OutputType for String {
type Events = events::StringEvents;
/// DOM output type EventTarget = ();
pub struct DOM; type EventListenerHandle = ();
impl OutputType for DOM {} }

View File

@ -0,0 +1,2 @@
#[cfg(feature = "stdweb")]
pub mod stdweb;

View File

@ -0,0 +1,201 @@
use std::fmt::{Display, Error, Formatter};
use std::marker::PhantomData;
use stdweb::web::event::*;
use stdweb::web::{self, Element, EventListenerHandle, IElement, IEventTarget, INode};
use super::super::OutputType;
use dom::VNode;
use events::{EventHandler, IntoEventHandler};
/// DOM output using the stdweb crate
pub struct Stdweb;
impl OutputType for Stdweb {
type Events = Events;
type EventTarget = Element;
type EventListenerHandle = EventListenerHandle;
}
macro_rules! declare_events {
($($name:ident : $type:ty ,)*) => {
/// Container type for DOM events.
pub struct Events {
$(
pub $name: Option<Box<dyn EventHandler<Stdweb, $type>>>,
)*
}
impl Default for Events {
fn default() -> Self {
Events {
$(
$name: None,
)*
}
}
}
/// Iterate over the defined events on a DOM object.
#[macro_export]
macro_rules! for_events {
($event:ident in $events:expr => $body:block) => {
$(
if let Some(ref mut $event) = $events.$name $body
)*
}
}
}
}
// TODO? these are all the "on*" attributes defined in the HTML5 standard, with
// the ones I've been unable to match to stdweb event types commented out.
//
// This needs review.
declare_events! {
abort: ResourceAbortEvent,
// autocomplete: Event,
// autocompleteerror: Event,
blur: BlurEvent,
// cancel: Event,
// canplay: Event,
// canplaythrough: Event,
change: ChangeEvent,
click: ClickEvent,
// close: Event,
contextmenu: ContextMenuEvent,
// cuechange: Event,
dblclick: DoubleClickEvent,
drag: DragEvent,
dragend: DragEndEvent,
dragenter: DragEnterEvent,
dragexit: DragExitEvent,
dragleave: DragLeaveEvent,
dragover: DragOverEvent,
dragstart: DragStartEvent,
drop: DragDropEvent,
// durationchange: Event,
// emptied: Event,
// ended: Event,
error: ResourceErrorEvent,
focus: FocusEvent,
input: InputEvent,
// invalid: Event,
keydown: KeyDownEvent,
keypress: KeyPressEvent,
keyup: KeyUpEvent,
load: ResourceLoadEvent,
// loadeddata: Event,
// loadedmetadata: Event,
loadstart: LoadStartEvent,
mousedown: MouseDownEvent,
mouseenter: MouseEnterEvent,
mouseleave: MouseLeaveEvent,
mousemove: MouseMoveEvent,
mouseout: MouseOutEvent,
mouseover: MouseOverEvent,
mouseup: MouseUpEvent,
mousewheel: MouseWheelEvent,
// pause: Event,
// play: Event,
// playing: Event,
progress: ProgressEvent,
// ratechange: Event,
// reset: Event,
resize: ResizeEvent,
scroll: ScrollEvent,
// seeked: Event,
// seeking: Event,
// select: Event,
// show: Event,
// sort: Event,
// stalled: Event,
submit: SubmitEvent,
// suspend: Event,
// timeupdate: Event,
// toggle: Event,
// volumechange: Event,
// waiting: Event,
}
impl Display for Events {
fn fmt(&self, _f: &mut Formatter) -> Result<(), Error> {
Ok(())
}
}
/// Wrapper type for closures as event handlers.
pub struct EFn<F, E>(Option<F>, PhantomData<E>);
impl<F, E> EFn<F, E>
where
F: FnMut(E) + 'static,
{
pub fn new(f: F) -> Self {
EFn(Some(f), PhantomData)
}
}
impl<F, E> IntoEventHandler<Stdweb, E> for F
where
F: FnMut(E) + 'static,
E: ConcreteEvent + 'static,
{
fn into_event_handler(self) -> Box<dyn EventHandler<Stdweb, E>> {
Box::new(EFn::new(self))
}
}
impl<F, E> IntoEventHandler<Stdweb, E> for EFn<F, E>
where
F: FnMut(E) + 'static,
E: ConcreteEvent + 'static,
{
fn into_event_handler(self) -> Box<dyn EventHandler<Stdweb, E>> {
Box::new(self)
}
}
impl<F, E> EventHandler<Stdweb, E> for EFn<F, E>
where
F: FnMut(E) + 'static,
E: ConcreteEvent + 'static,
{
fn attach(&mut self, target: &mut <Stdweb as OutputType>::EventTarget) -> EventListenerHandle {
let handler = self.0.take().unwrap();
target.add_event_listener(handler)
}
fn render(&self) -> Option<String> {
None
}
}
impl Stdweb {
pub fn install_handlers(target: &mut Element, handlers: &mut Events) {
for_events!(handler in handlers => {
handler.attach(target);
});
}
pub fn build(
document: &web::Document,
vnode: VNode<'_, Stdweb>,
) -> Result<web::Node, web::error::InvalidCharacterError> {
match vnode {
VNode::Text(text) => Ok(document.create_text_node(&text).into()),
VNode::Element(element) => {
let mut node = document.create_element(element.name)?;
for (key, value) in element.attributes {
node.set_attribute(&key, &value)?;
}
Stdweb::install_handlers(&mut node, element.events);
for child in element.children {
let child_node = Stdweb::build(document, child)?;
node.append_child(&child_node);
}
Ok(node.into())
}
}
}
}