Skip to content

Commit

Permalink
Receive Event in callback (Part 1) (#16)
Browse files Browse the repository at this point in the history
* Reorder declaration of TemplateResult

* Refactor element attribute parsing

* Add compile test for unknown directive

* Add hello world example

Demonstrates data binding

* Update README.md

* Fix broken test
  • Loading branch information
lukechu10 authored Mar 8, 2021
1 parent 3721245 commit ef0c6b7
Show file tree
Hide file tree
Showing 17 changed files with 207 additions and 139 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ members = [
"maple-core-macro",
"examples/components",
"examples/counter",
"examples/hello",
]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ template! {

// Events are attached using the `on:*` directive.
template! {
button(on:click=|| { /* do something */ }) {
button(on:click=|_| { /* do something */ }) {
# "Click me"
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/components/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn main() {
let increment = {
let state = state.clone();
let set_state = set_state.clone();
move || {
move |_| {
set_state(*state() + 1);
}
};
Expand Down
4 changes: 2 additions & 2 deletions examples/counter/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ fn main() {
let counter = counter.clone();
let set_counter = set_counter.clone();

move || set_counter(*counter() + 1)
move |_| set_counter(*counter() + 1)
};

let reset = {
let set_counter = set_counter.clone();

move || set_counter(0)
move |_| set_counter(0)
};

let root = template! {
Expand Down
18 changes: 18 additions & 0 deletions examples/hello/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
authors = ["Luke Chu <37006668+lukechu10@users.noreply.github.com>"]
edition = "2018"
name = "hello"
version = "0.1.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
console_error_panic_hook = "0.1.6"
console_log = "0.2.0"
log = "0.4.14"
maple-core = {path = "../../maple-core"}
wasm-bindgen = "0.2.71"

[dependencies.web-sys]
features = ["HtmlInputElement", "InputEvent"]
version = "0.3"
15 changes: 15 additions & 0 deletions examples/hello/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Hello World!</title>

<style>
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
}
</style>
</head>
<body></body>
</html>
45 changes: 45 additions & 0 deletions examples/hello/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#![allow(non_snake_case)]

use maple_core::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{Event, HtmlInputElement};

fn main() {
console_error_panic_hook::set_once();
console_log::init_with_level(log::Level::Debug).unwrap();

let (name, set_name) = create_signal(String::new());

let displayed_name = create_memo(move || {
if *name() == "" {
"World".to_string()
} else {
name().as_ref().clone()
}
});

let handle_change = move |event: Event| {
set_name(
event
.target()
.unwrap()
.dyn_into::<HtmlInputElement>()
.unwrap()
.value(),
);
};

let root = template! {
div {
h1 {
# "Hello "
# displayed_name()
# "!"
}

input(on:input=handle_change)
}
};

render(root);
}
72 changes: 72 additions & 0 deletions maple-core-macro/src/attributes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::token::Paren;
use syn::{parenthesized, Expr, Ident, Result, Token};

pub enum AttributeType {
/// Syntax: `name`.
DomAttribute { name: String },
/// Syntax: `on:name`.
Event { name: String },
}

impl Parse for AttributeType {
fn parse(input: ParseStream) -> Result<Self> {
let ident = input.call(Ident::parse_any)?;
let ident_str = ident.to_string();

if input.peek(Token![:]) {
let _colon: Token![:] = input.parse()?;
match ident_str.as_str() {
"on" => {
let event_name = input.call(Ident::parse_any)?;
Ok(Self::Event {
name: event_name.to_string(),
})
}
_ => Err(syn::Error::new_spanned(
ident,
format!("unknown directive `{}`", ident_str),
)),
}
} else {
Ok(Self::DomAttribute { name: ident_str })
}
}
}

pub struct Attribute {
pub ty: AttributeType,
pub equals_token: Token![=],
pub expr: Expr,
}

impl Parse for Attribute {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
ty: input.parse()?,
equals_token: input.parse()?,
expr: input.parse()?,
})
}
}

pub struct AttributeList {
pub paren_token: Paren,
pub attributes: Punctuated<Attribute, Token![,]>,
}

impl Parse for AttributeList {
fn parse(input: ParseStream) -> Result<Self> {
let content;
let paren_token = parenthesized!(content in input);

let attributes = content.parse_terminated(Attribute::parse)?;

Ok(Self {
paren_token,
attributes,
})
}
}
136 changes: 22 additions & 114 deletions maple-core-macro/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,27 @@ use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{
parenthesized, token, Expr, ExprAssign, ExprPath, ExprType, Ident, Token, Type, TypePath,
};
use syn::{token, Ident, Token};

use crate::attributes::{AttributeList, AttributeType};
use crate::children::Children;

/// Represents a html element with all its attributes and properties (e.g. `p(class="text")`).
pub(crate) struct Element {
tag_name: TagName,
_paren_token: Option<token::Paren>,
attributes: Punctuated<Expr, Token![,]>,
attributes: Option<AttributeList>,
children: Option<Children>,
}

impl Parse for Element {
fn parse(input: ParseStream) -> Result<Self> {
let tag_name = input.parse()?;
let (paren_token, attributes) = if input.peek(token::Paren) {
let attributes;
let paren_token = parenthesized!(attributes in input);
(Some(paren_token), attributes.parse_terminated(Expr::parse)?)

let attributes = if input.peek(token::Paren) {
Some(input.parse()?)
} else {
(None, Punctuated::new())
None
};

let children = if input.peek(token::Brace) {
Expand All @@ -35,69 +31,8 @@ impl Parse for Element {
None
};

// check attribute syntax
for attribute in &attributes {
match attribute {
Expr::Assign(ExprAssign {
attrs: _,
left,
eq_token: _,
right: _,
}) => {
match left.as_ref() {
// simple attribute (e.g. `disabled`)
Expr::Path(ExprPath { path, .. }) if path.segments.len() == 1 => {}
// `on:click` is parsed as a type ascription expression
Expr::Type(ExprType {
attrs: _,
expr,
colon_token: _,
ty,
}) => match expr.as_ref() {
Expr::Path(ExprPath { path, .. }) if path.segments.len() == 1 => {
match path.segments[0].ident.to_string().as_str() {
"on" => {}
_ => {
return Err(syn::Error::new_spanned(
&path.segments[0],
format!(
"unknown directive `{}`",
path.segments[0].ident
),
))
}
}

match ty.as_ref() {
Type::Path(TypePath { path, .. })
if path.segments.len() == 1 => {}
_ => {
return Err(syn::Error::new_spanned(
ty,
"expected an identifier",
))
}
}
}
_ => {
return Err(syn::Error::new_spanned(expr, "expected an identifier"))
}
},
_ => return Err(syn::Error::new_spanned(left, "unexpected token")),
}
}
_ => {
return Err(syn::Error::new_spanned(
attribute,
"expected an assignment expression",
))
}
}
}

Ok(Self {
tag_name,
_paren_token: paren_token,
attributes,
children,
})
Expand All @@ -108,56 +43,29 @@ impl ToTokens for Element {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Element {
tag_name,
_paren_token: _,
attributes,
children,
} = self;

let mut set_attributes = Vec::new();
let mut set_event_listeners = Vec::new();
for attribute in attributes {
let attribute_span = attribute.span();

match attribute {
Expr::Assign(ExprAssign {
attrs: _,
left,
eq_token: _,
right,
}) => match left.as_ref() {
Expr::Path(_) => {
let left_str = left.to_token_stream().to_string();

set_attributes.push(quote_spanned! { attribute_span=>
::maple_core::internal::attr(&element, #left_str, move || ::std::format!("{}", #right));
if let Some(attributes) = attributes {
for attribute in &attributes.attributes {
let expr = &attribute.expr;
let expr_span = expr.span();

match &attribute.ty {
AttributeType::DomAttribute { name } => {
set_attributes.push(quote_spanned! { expr_span=>
::maple_core::internal::attr(&element, #name, move || ::std::format!("{}", #expr));
});
}
AttributeType::Event { name } => {
set_event_listeners.push(quote_spanned! { expr_span=>
::maple_core::internal::event(&element, #name, ::std::boxed::Box::new(#expr));
});
}
Expr::Type(ExprType {
attrs: _,
expr,
colon_token: _,
ty,
}) => match expr.as_ref() {
Expr::Path(path) => {
let directive = path.to_token_stream().to_string();

match directive.as_str() {
"on" => {
// attach event handler
let event_name = ty.to_token_stream().to_string();

set_event_listeners.push(quote_spanned! { attribute_span=>
::maple_core::internal::event(&element, #event_name, ::std::boxed::Box::new(#right));
});
}
_ => unreachable!("attribute syntax checked during parsing"),
}
}
_ => unreachable!("attribute syntax checked during parsing"),
},
_ => unreachable!("attribute syntax checked during parsing"),
},
_ => unreachable!("attribute syntax checked during parsing"),
}
}
}

Expand Down
1 change: 1 addition & 0 deletions maple-core-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod attributes;
mod children;
mod component;
mod element;
Expand Down
2 changes: 1 addition & 1 deletion maple-core-macro/tests/ui/component-fail.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: expected an assignment expression
error: expected ident
--> $DIR/component-fail.rs:15:27
|
15 | template! { Component(1) };
Expand Down
1 change: 1 addition & 0 deletions maple-core-macro/tests/ui/element-fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ fn compile_fail() {

template! { button(disabled) };
template! { button(on:click) };
template! { button(unknown:directive="123") };

template! { button(a.b.c="123") };
}
Expand Down
Loading

0 comments on commit ef0c6b7

Please sign in to comment.