Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Receive Event in callback (Part 1) #16

Merged
merged 6 commits into from
Mar 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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