Event handlers!
This commit is contained in:
parent
ca77a12599
commit
a9dc58da9c
|
@ -31,6 +31,75 @@ pub fn global_attrs(span: Span) -> StringyMap<Ident, TokenStream> {
|
|||
insert("style", "String");
|
||||
insert("tabindex", "isize");
|
||||
insert("title", "String");
|
||||
|
||||
// FIXME ARIA and XML attrs missing
|
||||
}
|
||||
attrs
|
||||
}
|
||||
|
||||
// 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,6 +1,6 @@
|
|||
use proc_macro::{quote, Ident, Literal, TokenStream, TokenTree};
|
||||
use proc_macro::{quote, Ident, Literal, Span, TokenStream, TokenTree};
|
||||
|
||||
use config::global_attrs;
|
||||
use config::{global_attrs, ATTR_EVENTS};
|
||||
use error::ParseError;
|
||||
use lexer::{Lexer, Token};
|
||||
use map::StringyMap;
|
||||
|
@ -52,8 +52,7 @@ impl Declare {
|
|||
self.req_children.iter().map(|child| {
|
||||
let child_name: TokenTree =
|
||||
Ident::new(&format!("child_{}", child.to_string()), child.span()).into();
|
||||
let child_type: TokenTree =
|
||||
Ident::new(&format!("{}", child.to_string()), child.span()).into();
|
||||
let child_type: TokenTree = Ident::new(&child.to_string(), child.span()).into();
|
||||
let child_str = Literal::string(&child.to_string()).into();
|
||||
(child_name, child_type, child_str)
|
||||
})
|
||||
|
@ -103,7 +102,8 @@ impl Declare {
|
|||
quote!(
|
||||
pub struct $elem_name {
|
||||
pub attrs: $attr_type_name,
|
||||
pub data_attributes: Vec<(String, String)>,
|
||||
pub data_attributes: Vec<(&'static str, String)>,
|
||||
pub events: ::events::Events,
|
||||
$body
|
||||
}
|
||||
)
|
||||
|
@ -128,6 +128,7 @@ impl Declare {
|
|||
attrs: $attr_type_name { $attrs },
|
||||
));
|
||||
body.extend(quote!(data_attributes: Vec::new(),));
|
||||
|
||||
for (child_name, _, _) in self.req_children() {
|
||||
body.extend(quote!( $child_name, ));
|
||||
}
|
||||
|
@ -139,6 +140,7 @@ impl Declare {
|
|||
impl $elem_name {
|
||||
pub fn new($args) -> Self {
|
||||
$elem_name {
|
||||
events: ::events::Events::default(),
|
||||
$body
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +158,7 @@ impl Declare {
|
|||
}
|
||||
let mut opt_children = TokenStream::new();
|
||||
if self.opt_children.is_some() {
|
||||
opt_children.extend(quote!(for child in &self.children {
|
||||
opt_children.extend(quote!(for child in &mut self.children {
|
||||
children.push(child.vnode());
|
||||
}));
|
||||
}
|
||||
|
@ -165,7 +167,7 @@ impl Declare {
|
|||
for (attr_name, _, attr_str) in self.attrs() {
|
||||
push_attrs.extend(quote!(
|
||||
if let Some(ref value) = self.attrs.$attr_name {
|
||||
attributes.push(($attr_str.to_string(), value.to_string()));
|
||||
attributes.push(($attr_str, value.to_string()));
|
||||
}
|
||||
));
|
||||
}
|
||||
|
@ -173,9 +175,7 @@ impl Declare {
|
|||
quote!(
|
||||
let mut attributes = Vec::new();
|
||||
$push_attrs
|
||||
for (key, value) in &self.data_attributes {
|
||||
attributes.push((format!("data-{}", key), value.to_string()));
|
||||
}
|
||||
attributes.extend(self.data_attributes.clone());
|
||||
|
||||
let mut children = Vec::new();
|
||||
$req_children
|
||||
|
@ -184,6 +184,7 @@ impl Declare {
|
|||
::dom::VNode::Element(::dom::VElement {
|
||||
name: $elem_name,
|
||||
attributes,
|
||||
events: &mut self.events,
|
||||
children
|
||||
})
|
||||
)
|
||||
|
@ -194,7 +195,7 @@ impl Declare {
|
|||
let vnode = self.impl_vnode();
|
||||
quote!(
|
||||
impl ::dom::Node for $elem_name {
|
||||
fn vnode(&self) -> ::dom::VNode {
|
||||
fn vnode<'a>(&'a mut self) -> ::dom::VNode<'a> {
|
||||
$vnode
|
||||
}
|
||||
}
|
||||
|
@ -215,7 +216,7 @@ impl Declare {
|
|||
for (attr_name, _, attr_str) in self.attrs() {
|
||||
push_attrs.extend(quote!(
|
||||
if let Some(ref value) = self.attrs.$attr_name {
|
||||
out.push(($attr_str.to_string(), value.to_string()));
|
||||
out.push(($attr_str, value.to_string()));
|
||||
}
|
||||
));
|
||||
}
|
||||
|
@ -234,11 +235,11 @@ impl Declare {
|
|||
&[ $reqs ]
|
||||
}
|
||||
|
||||
fn attributes(&self) -> Vec<(String, String)> {
|
||||
fn attributes(&self) -> Vec<(&'static str, String)> {
|
||||
let mut out = Vec::new();
|
||||
$push_attrs
|
||||
for (key, value) in &self.data_attributes {
|
||||
out.push((format!("data-{}", key), value.to_string()));
|
||||
out.push((key, value.to_string()));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
@ -308,6 +309,18 @@ 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()))?;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
quote!(
|
||||
impl std::fmt::Display for $elem_name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||
|
@ -317,6 +330,7 @@ impl Declare {
|
|||
write!(f, " data-{}=\"{}\"", key,
|
||||
::htmlescape::encode_attribute(&value))?;
|
||||
}
|
||||
$print_events
|
||||
$print_children
|
||||
}
|
||||
}
|
||||
|
|
|
@ -198,7 +198,28 @@ TypeArgList: Vec<Token> = "<" TypeArgs ">" => {
|
|||
args
|
||||
};
|
||||
|
||||
TypeSpec: Vec<Token> = Reference? TypePath TypeArgList? => {
|
||||
FnReturnType: Vec<Token> = "-" ">" TypeSpec => {
|
||||
let (dash, right, spec) = (<>);
|
||||
let mut out = vec![dash, right];
|
||||
out.extend(spec);
|
||||
out
|
||||
};
|
||||
|
||||
FnArgList: Vec<Token> = ParenGroupToken FnReturnType? => {
|
||||
let (args, rt) = (<>);
|
||||
let mut out = vec![args];
|
||||
if let Some(rt) = rt {
|
||||
out.extend(rt);
|
||||
}
|
||||
out
|
||||
};
|
||||
|
||||
TypeArgSpec = {
|
||||
TypeArgList,
|
||||
FnArgList,
|
||||
};
|
||||
|
||||
TypeSpec: Vec<Token> = Reference? TypePath TypeArgSpec? => {
|
||||
let (reference, path, args) = (<>);
|
||||
let mut out = Vec::new();
|
||||
if let Some(reference) = reference {
|
||||
|
|
|
@ -2,7 +2,7 @@ use proc_macro::{
|
|||
quote, Delimiter, Diagnostic, Group, Ident, Level, Literal, TokenStream, TokenTree,
|
||||
};
|
||||
|
||||
use config::required_children;
|
||||
use config::{required_children, ATTR_EVENTS};
|
||||
use error::ParseError;
|
||||
use lexer::{Lexer, Token};
|
||||
use map::StringyMap;
|
||||
|
@ -68,12 +68,31 @@ fn extract_data_attrs(attrs: &mut StringyMap<Ident, TokenTree>) -> StringyMap<St
|
|||
let prefix = "data_";
|
||||
if key_name.starts_with(prefix) {
|
||||
let value = attrs.remove(&key).unwrap();
|
||||
data.insert(key_name[prefix.len()..].to_string(), value);
|
||||
data.insert(format!("data-{}", &key_name[prefix.len()..]), value);
|
||||
}
|
||||
}
|
||||
data
|
||||
}
|
||||
|
||||
fn extract_event_handlers(
|
||||
attrs: &mut StringyMap<Ident, TokenTree>,
|
||||
) -> StringyMap<Ident, TokenTree> {
|
||||
let mut events = StringyMap::new();
|
||||
let keys: Vec<Ident> = attrs.keys().cloned().collect();
|
||||
for key in keys {
|
||||
let key_name = key.to_string();
|
||||
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
|
||||
}
|
||||
|
||||
fn process_value(value: &TokenTree) -> TokenStream {
|
||||
match value {
|
||||
TokenTree::Group(g) if g.delimiter() == Delimiter::Bracket => {
|
||||
|
@ -113,6 +132,7 @@ impl Element {
|
|||
.emit();
|
||||
panic!();
|
||||
}
|
||||
let events = extract_event_handlers(&mut self.attributes);
|
||||
let data_attrs = extract_data_attrs(&mut self.attributes);
|
||||
let attrs = self.attributes.iter().map(|(key, value)| {
|
||||
(
|
||||
|
@ -164,11 +184,19 @@ impl Element {
|
|||
.map(|(k, v)| (TokenTree::from(Literal::string(&k)), v.clone()))
|
||||
{
|
||||
body.extend(quote!(
|
||||
element.data_attributes.push(($key.into(), $value.into()));
|
||||
element.data_attributes.push(($key, $value.into()));
|
||||
));
|
||||
}
|
||||
body.extend(opt_children);
|
||||
|
||||
for (key, value) in events.iter() {
|
||||
let key = TokenTree::Ident(key.clone());
|
||||
let value = process_value(value);
|
||||
body.extend(quote!(
|
||||
element.events.$key = Some(Box::new($value));
|
||||
));
|
||||
}
|
||||
|
||||
let mut args = TokenStream::new();
|
||||
for arg in req_children {
|
||||
args.extend(quote!( $arg, ));
|
||||
|
|
|
@ -117,7 +117,7 @@ pub fn unroll_stream(stream: TokenStream, deep: bool) -> Vec<Token> {
|
|||
TokenTree::Ident(ident) => vec.push(ident.into()),
|
||||
TokenTree::Literal(literal) => vec.push(literal.into()),
|
||||
TokenTree::Punct(punct) => vec.push(punct.into()),
|
||||
TokenTree::Group(ref group) if deep => {
|
||||
TokenTree::Group(ref group) if deep && group.delimiter() != Delimiter::Parenthesis => {
|
||||
vec.push(Token::GroupOpen(group.delimiter(), group.span()));
|
||||
let sub = unroll_stream(group.stream(), deep);
|
||||
vec.extend(sub);
|
||||
|
|
|
@ -31,6 +31,7 @@ fn index() -> Content<String> {
|
|||
})
|
||||
}
|
||||
<p>"<img src=\"javascript:alert('pwned lol')\">"</p>
|
||||
<button onclick="alert('She is not a cat.')">"Click me!"</button>
|
||||
</body>
|
||||
</html>
|
||||
).to_string())
|
||||
|
|
|
@ -12,3 +12,4 @@ language-tags = "0.2.2"
|
|||
enumset = "0.3.12"
|
||||
http = "0.1.13"
|
||||
htmlescape = "0.3.1"
|
||||
stdweb = "0.4.10"
|
||||
|
|
|
@ -35,9 +35,9 @@ fn main() {
|
|||
})
|
||||
}
|
||||
<p>"<img src=\"javascript:alert('pwned lol')\">"</p>
|
||||
<button onclick="alert('lol')">"lol"</button>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
println!("{}", doc.to_string());
|
||||
println!("{:?}", doc.vnode());
|
||||
}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
//! DOM and virtual DOM types.
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use elements::{FlowContent, PhrasingContent};
|
||||
use events::Events;
|
||||
use htmlescape::encode_minimal;
|
||||
|
||||
/// An untyped representation of an HTML node.
|
||||
|
@ -23,18 +21,17 @@ use htmlescape::encode_minimal;
|
|||
/// ```
|
||||
///
|
||||
/// [Node]: trait.Node.html
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum VNode {
|
||||
Text(String),
|
||||
Element(VElement),
|
||||
pub enum VNode<'a> {
|
||||
Text(&'a str),
|
||||
Element(VElement<'a>),
|
||||
}
|
||||
|
||||
/// An untyped representation of an HTML element.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VElement {
|
||||
pub struct VElement<'a> {
|
||||
pub name: &'static str,
|
||||
pub attributes: Vec<(String, String)>,
|
||||
pub children: Vec<VNode>,
|
||||
pub attributes: Vec<(&'static str, String)>,
|
||||
pub events: &'a mut Events,
|
||||
pub children: Vec<VNode<'a>>,
|
||||
}
|
||||
|
||||
/// Trait for rendering a typed HTML node.
|
||||
|
@ -53,7 +50,7 @@ pub trait Node: Display {
|
|||
/// Render the node into a [`VNode`][VNode] tree.
|
||||
///
|
||||
/// [VNode]: enum.VNode.html
|
||||
fn vnode(&self) -> VNode;
|
||||
fn vnode<'a>(&'a mut self) -> VNode<'a>;
|
||||
}
|
||||
|
||||
/// Trait for querying a typed HTML element.
|
||||
|
@ -79,7 +76,7 @@ pub trait Element: Node {
|
|||
///
|
||||
/// This will convert attribute values into strings and return a vector of
|
||||
/// key/value pairs.
|
||||
fn attributes(&self) -> Vec<(String, String)>;
|
||||
fn attributes(&self) -> Vec<(&'static str, String)>;
|
||||
}
|
||||
|
||||
/// An HTML text node.
|
||||
|
@ -134,10 +131,11 @@ impl Display for TextNode {
|
|||
}
|
||||
|
||||
impl Node for TextNode {
|
||||
fn vnode(&self) -> VNode {
|
||||
VNode::Text(self.0.clone())
|
||||
fn vnode<'a>(&'a mut self) -> VNode<'a> {
|
||||
VNode::Text(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for TextNode {
|
||||
type Item = TextNode;
|
||||
type IntoIter = std::vec::IntoIter<TextNode>;
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
use std::marker::PhantomData;
|
||||
use stdweb::web::event::*;
|
||||
use stdweb::web::{Element, EventListenerHandle, IEventTarget};
|
||||
|
||||
macro_rules! declare_events {
|
||||
($($name:ident : $type:ty ,)*) => {
|
||||
#[derive(Default)]
|
||||
pub struct Events {
|
||||
$(
|
||||
pub $name: Option<Box<dyn EventHandler<$type>>>,
|
||||
)*
|
||||
}
|
||||
|
||||
#[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.
|
||||
pub trait EventHandler<EventType> {
|
||||
/// Build a callback function from this event handler.
|
||||
///
|
||||
/// Returns `None` is this event handler can't be used to build a callback
|
||||
/// function. This is usually the case if the event handler is a string
|
||||
/// intended for server side rendering.
|
||||
// fn build(self) -> Option<Box<FnMut(EventType) + 'static>>;
|
||||
|
||||
fn attach(&mut self, target: &Element) -> EventListenerHandle;
|
||||
|
||||
/// Render this event handler as a string.
|
||||
///
|
||||
/// Returns `None` if this event handler cannot be rendered. Normally, the
|
||||
/// only event handlers that can be rendered are string values intended for
|
||||
/// server side rendering.
|
||||
fn render(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
pub struct EFn<F, E>(Option<F>, PhantomData<E>);
|
||||
|
||||
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> EventHandler<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<'a, EventType> EventHandler<EventType> for &'a str {
|
||||
fn attach(&mut self, _target: &Element) -> EventListenerHandle {
|
||||
panic!("Silly wabbit, strings as event handlers are only for printing.");
|
||||
}
|
||||
|
||||
fn render(&self) -> Option<String> {
|
||||
Some(self.to_string())
|
||||
}
|
||||
}
|
|
@ -9,9 +9,11 @@ pub extern crate htmlescape;
|
|||
extern crate http;
|
||||
extern crate language_tags;
|
||||
extern crate mime;
|
||||
extern crate stdweb;
|
||||
extern crate strum;
|
||||
extern crate typed_html_macros;
|
||||
|
||||
pub mod dom;
|
||||
pub mod elements;
|
||||
pub mod events;
|
||||
pub mod types;
|
||||
|
|
|
@ -5,10 +5,18 @@ extern crate stdweb;
|
|||
extern crate typed_html;
|
||||
extern crate typed_html_macros;
|
||||
|
||||
use stdweb::web::{self, IElement, INode};
|
||||
use stdweb::web::{self, Element, IElement, INode};
|
||||
use typed_html::for_events;
|
||||
use typed_html::dom::{Node, VNode};
|
||||
use typed_html::events::{EFn, Events};
|
||||
use typed_html_macros::html;
|
||||
|
||||
fn install_handlers(target: &Element, handlers: &mut Events) {
|
||||
for_events!(handler in handlers => {
|
||||
handler.attach(target);
|
||||
});
|
||||
}
|
||||
|
||||
fn build(
|
||||
document: &web::Document,
|
||||
vnode: VNode,
|
||||
|
@ -20,6 +28,7 @@ fn build(
|
|||
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);
|
||||
|
@ -30,13 +39,18 @@ fn build(
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let doc = html!(
|
||||
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=EFn::new(|_event| web::alert("Hello Joe!"))>
|
||||
"Call Joe"
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
let vdom = doc.vnode();
|
||||
|
|
Loading…
Reference in New Issue