Merge pull request #22 from bodil/generic-events

Generic event types.
This commit is contained in:
Bodil Stokke 2018-12-03 17:03:38 +00:00 committed by GitHub
commit 4c49aca99f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 457 additions and 380 deletions

View File

@ -9,6 +9,6 @@ matrix:
- rust: nightly
script: cargo test
- rust: beta
script: cargo check --manifest-path examples/wasm/Cargo.toml
script: cargo check --manifest-path examples/stdweb/Cargo.toml
- rust: stable
script: cargo check --manifest-path examples/wasm/Cargo.toml
script: cargo check --manifest-path examples/stdweb/Cargo.toml

View File

@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic
Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.1.1] - 2018-22-29
### Added

View File

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

View File

@ -9,8 +9,8 @@ use rocket::http::{ContentType, Status};
use rocket::response::{Responder, Result};
use rocket::{get, routes, Request, Response};
use std::io::Cursor;
use typed_html::types::LinkType;
use typed_html::elements::FlowContent;
use typed_html::types::LinkType;
use typed_html::{dom::DOMTree, html, text, OutputType};
struct Html(DOMTree<String>);
@ -64,7 +64,7 @@ fn index() -> Html {
<p>"<img src=\"javascript:alert('pwned lol')\">"</p>
<button disabled=a onclick="alert('She is not a cat.')">"Click me!"</button>
</div>
)))
: String)))
}
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",
"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 config::{global_attrs, ATTR_EVENTS, SELF_CLOSING};
use config::{global_attrs, SELF_CLOSING};
use error::ParseError;
use ident;
use lexer::{Lexer, Token};
@ -104,10 +104,9 @@ impl Declare {
quote!(
pub struct #elem_name<T> where T: ::OutputType {
phantom_output: std::marker::PhantomData<T>,
pub attrs: #attr_type_name,
pub data_attributes: Vec<(&'static str, String)>,
pub events: ::events::Events<T>,
pub events: T::Events,
#body
}
)
@ -144,8 +143,7 @@ impl Declare {
impl<T> #elem_name<T> where T: ::OutputType {
pub fn new(#args) -> Self {
#elem_name {
phantom_output: std::marker::PhantomData,
events: ::events::Events::default(),
events: T::Events::default(),
#body
}
}
@ -307,13 +305,11 @@ impl Declare {
let print_children = if self.req_children.is_empty() {
if self.opt_children.is_some() {
if !SELF_CLOSING.contains(&elem_name.to_string().as_str()) {
quote!(if self.children.is_empty() {
write!(f, "></{}>", #name)
} else {
quote!(
write!(f, ">")?;
#print_opt_children
write!(f, "</{}>", #name)
})
)
} else {
quote!(if self.children.is_empty() {
write!(f, " />")
@ -323,13 +319,11 @@ impl Declare {
write!(f, "</{}>", #name)
})
}
} else {
if !SELF_CLOSING.contains(&elem_name.to_string().as_str()) {
} else if !SELF_CLOSING.contains(&elem_name.to_string().as_str()) {
quote!(write!(f, "></{}>", #name))
} else {
quote!(write!(f, "/>"))
}
}
} else {
quote!(
write!(f, ">")?;
@ -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!(
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> {
write!(f, "<{}", #name)?;
#print_attrs
@ -370,7 +355,7 @@ impl Declare {
write!(f, " data-{}=\"{}\"", key,
::htmlescape::encode_attribute(&value))?;
}
#print_events
write!(f, "{}", self.events)?;
#print_children
}
}

View File

@ -160,12 +160,19 @@ CodeBlock: Group = BraceGroupToken => match <> {
_ => unreachable!()
};
pub Node: Node = {
Node: Node = {
Element => Node::Element(<>),
TextNode => Node::Text(<>),
CodeBlock => Node::Block(<>),
};
pub NodeWithType: (Node, Option<Vec<Token>>) = {
Node => (<>, None),
<Node> ":" <TypeSpec> => {
let (node, spec) = (<>);
(node, Some(spec))
},
};
// 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, Span, TokenStream, TokenTree};
use quote::{quote, quote_spanned};
use config::{required_children, ATTR_EVENTS};
use config::required_children;
use error::ParseError;
use ident;
use lexer::{Lexer, Token};
use lexer::{to_stream, Lexer, Token};
use map::StringyMap;
use parser::grammar;
@ -18,33 +18,34 @@ pub enum 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 {
Node::Element(el) => el.into_token_stream(),
Node::Element(el) => el.into_token_stream(ty),
Node::Text(text) => {
let text = TokenTree::Literal(text);
Ok(quote!(Box::new(typed_html::dom::TextNode::new(#text.to_string()))))
}
Node::Block(group) => {
let span = group.span();
let error = "you cannot use a block as a top level element or a required child element";
Err(quote_spanned!{ span=>
let error =
"you cannot use a block as a top level element or a required child element";
Err(quote_spanned! { span=>
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 {
Node::Element(el) => {
let el = el.into_token_stream()?;
let el = el.into_token_stream(ty)?;
Ok(quote!(
element.children.push(#el);
))
}
tx @ Node::Text(_) => {
let tx = tx.into_token_stream()?;
let tx = tx.into_token_stream(ty)?;
Ok(quote!(
element.children.push(#tx);
))
@ -92,12 +93,10 @@ fn extract_event_handlers(
let prefix = "on";
if key_name.starts_with(prefix) {
let event_name = &key_name[prefix.len()..];
if ATTR_EVENTS.binary_search(&event_name).is_ok() {
let value = attrs.remove(&key).unwrap();
events.insert(ident::new_raw(event_name, key.span()), value);
}
}
}
events
}
@ -121,7 +120,7 @@ fn is_string_literal(literal: &Literal) -> bool {
}
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_str = name.to_string();
let typename: TokenTree = Ident::new(&name_str, name.span()).into();
@ -151,12 +150,12 @@ impl Element {
.children
.split_off(req_names.len())
.into_iter()
.map(Node::into_child_stream)
.map(|node| node.into_child_stream(ty))
.collect::<Result<Vec<TokenStream>, TokenStream>>()?;
let req_children = self
.children
.into_iter()
.map(Node::into_token_stream)
.map(|node| node.into_token_stream(ty))
.collect::<Result<Vec<TokenStream>, TokenStream>>()?;
let mut body = TokenStream::new();
@ -214,6 +213,16 @@ impl Element {
body.extend(opt_children);
for (key, value) in events.iter() {
if ty.is_none() {
let mut err = quote_spanned! { key.span() =>
compile_error! { "when using event handlers, you must declare the output type inside the html! macro" }
};
let hint = quote_spanned! { Span::call_site() =>
compile_error! { "for example: change html!(<div>...</div>) to html!(<div>...</div> : String)" }
};
err.extend(hint);
return Err(err);
}
let key = TokenTree::Ident(key.clone());
let value = process_value(value);
body.extend(quote!(
@ -226,9 +235,15 @@ impl Element {
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!(
{
let mut element = typed_html::elements::#typename::new(#args);
let mut element #type_annotation = typed_html::elements::#typename::new(#args);
#body
Box::new(element)
}
@ -237,6 +252,6 @@ impl Element {
}
// FIXME report a decent error when the macro contains multiple top level elements
pub fn expand_html(input: &[Token]) -> Result<Node, ParseError> {
grammar::NodeParser::new().parse(Lexer::new(input))
pub fn expand_html(input: &[Token]) -> Result<(Node, Option<Vec<Token>>), ParseError> {
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);
TokenStream::from(match result {
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,
Ok(success) => success,
},

View File

@ -15,13 +15,13 @@ travis-ci = { repository = "bodil/typed-html" }
maintenance = { status = "actively-developed" }
[dependencies]
typed-html-macros = "0.1.1"
typed-html-macros = { path = "../macros" }
strum = "0.11.0"
strum_macros = "0.11.0"
mime = "0.3.12"
language-tags = "0.2.2"
http = "0.1.13"
htmlescape = "0.3.1"
stdweb = "0.4.10"
proc-macro-hack = "0.5.2"
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 elements::{FlowContent, PhrasingContent};
use events::Events;
use htmlescape::encode_minimal;
/// 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 name: &'static str,
pub attributes: Vec<(&'static str, String)>,
pub events: &'a mut Events<T>,
pub events: &'a mut T::Events,
pub children: Vec<VNode<'a, T>>,
}
@ -73,7 +72,10 @@ pub trait Node<T: OutputType>: Display {
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 IntoIter = std::vec::IntoIter<Box<dyn Node<T>>>;

View File

@ -1,129 +1,8 @@
//! Event handlers.
use std::marker::PhantomData;
use stdweb::web::event::*;
use stdweb::web::{Element, EventListenerHandle, IEventTarget};
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,
}
use super::OutputType;
use htmlescape::encode_attribute;
use std::fmt::{Display, Error, Formatter};
/// Trait for event handlers.
pub trait EventHandler<T: OutputType, E> {
@ -134,7 +13,7 @@ pub trait EventHandler<T: OutputType, E> {
/// intended for server side rendering.
// 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.
///
@ -150,56 +29,11 @@ pub trait IntoEventHandler<T: OutputType, E> {
fn into_event_handler(self) -> Box<dyn EventHandler<T, E>>;
}
/// Wrapper type for closures as event handlers.
pub struct EFn<F, E>(Option<F>, PhantomData<E>);
/// An uninhabited event type for string handlers.
pub enum StringEvent {}
impl<F, E> EFn<F, E>
where
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 {
impl EventHandler<String, StringEvent> for &'static str {
fn attach(&mut self, _target: &mut <String as OutputType>::EventTarget) {
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 {
fn into_event_handler(self) -> Box<dyn EventHandler<String, E>> {
impl IntoEventHandler<String, StringEvent> for &'static str {
fn into_event_handler(self) -> Box<dyn EventHandler<String, StringEvent>> {
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"]
//! # use typed_html::{html, for_events};
//! # use typed_html::html;
//! # use typed_html::dom::{DOMTree, VNode};
//! # use typed_html::types::Metadata;
//! # fn main() {
@ -200,11 +200,14 @@ extern crate language_tags;
extern crate mime;
extern crate proc_macro_hack;
extern crate proc_macro_nested;
extern crate stdweb;
extern crate strum;
extern crate typed_html_macros;
#[cfg(feature = "stdweb")]
extern crate stdweb;
use proc_macro_hack::proc_macro_hack;
use std::fmt::Display;
#[proc_macro_hack(support_nested)]
pub use typed_html_macros::html;
@ -212,14 +215,22 @@ pub use typed_html_macros::html;
pub mod dom;
pub mod elements;
pub mod events;
pub mod output;
pub mod types;
/// 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
impl OutputType for String {}
/// DOM output
pub struct DOM;
impl OutputType for DOM {}
impl OutputType for String {
type Events = events::StringEvents;
type EventTarget = ();
type EventListenerHandle = ();
}

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())
}
}
}
}