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:
parent
77a3c6d840
commit
818c8ebdac
|
@ -2,7 +2,7 @@
|
||||||
members = [
|
members = [
|
||||||
"typed-html",
|
"typed-html",
|
||||||
"macros",
|
"macros",
|
||||||
"examples/wasm",
|
"examples/stdweb",
|
||||||
"examples/rocket",
|
"examples/rocket",
|
||||||
"ui",
|
"ui",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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"
|
|
@ -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);
|
||||||
|
}
|
|
@ -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"
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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",
|
|
||||||
];
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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>>>;
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
|
@ -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 {}
|
}
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
#[cfg(feature = "stdweb")]
|
||||||
|
pub mod stdweb;
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue