commit
4c49aca99f
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
members = [
|
||||
"typed-html",
|
||||
"macros",
|
||||
"examples/wasm",
|
||||
"examples/stdweb",
|
||||
"examples/rocket",
|
||||
"ui",
|
||||
]
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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",
|
||||
"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 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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>>>;
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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 = ();
|
||||
}
|
||||
|
|
|
@ -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