Skip to content

Commit 0aa3789

Browse files
committed
Initial prototype - I can haz rust in browser
0 parents  commit 0aa3789

File tree

6 files changed

+246
-0
lines changed

6 files changed

+246
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
target
2+
Cargo.lock
3+
static/*.js

Cargo.toml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "quasar"
3+
version = "0.1.0"
4+
authors = ["Anthony Nowell <[email protected]>"]
5+
6+
[dependencies]
7+
mustache = "0.8.0"
8+
webplatform = "0.4.2"
9+
rustc-serialize = "0.3.18"

bin/build-asm

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
#
3+
# Usage:
4+
# bin/build-asm test
5+
# bin/build-asm examples
6+
# bin/build-asm <example>
7+
8+
ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )"
9+
echo $ROOT
10+
cargo build --target=asmjs-unknown-emscripten
11+
12+
item=$1
13+
if [[ "$item" = "test" ]]; then
14+
cargo test --target=asmjs-unknown-emscripten --no-run
15+
node $ROOT/target/asmjs-unknown-emscripten/debug/deps/pulsar-*.js
16+
elif [[ "$item" = "examples" ]]; then
17+
cargo test --target=asmjs-unknown-emscripten --no-run
18+
cp $ROOT/target/asmjs-unknown-emscripten/debug/examples/*.js $ROOT/static/
19+
elif [[ -n "$item" ]]; then
20+
cargo test --example $item --target=asmjs-unknown-emscripten
21+
cp $ROOT/target/asmjs-unknown-emscripten/debug/examples/${item}.js $ROOT/static/
22+
fi

examples/reverser.rs

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
extern crate quasar;
2+
extern crate rustc_serialize;
3+
4+
use quasar::{EventType};
5+
6+
#[derive(RustcEncodable)]
7+
struct ReverseData {
8+
message: String,
9+
}
10+
11+
fn main() {
12+
let mut app = quasar::init();
13+
14+
let data = ReverseData{ message: "Initial Message".to_owned() };
15+
let view = app.bind("#reverser", data);
16+
17+
// let view: View<ReverserData> = app.view("#reverser").unwrap()
18+
19+
// This is basically useless, since we're
20+
// blocking the event loop until `app` drops
21+
view.update( |ref mut data| {
22+
println!("{}", data.message);
23+
data.message = "Hello World".to_owned();
24+
});
25+
26+
// TODO: subquery via view.query("button").on...
27+
view.on(EventType::Click, |ref mut data| {
28+
data.message = data.message.chars().rev().collect::<String>();
29+
});
30+
31+
println!("End of main");
32+
}

src/lib.rs

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
extern crate webplatform;
2+
extern crate mustache;
3+
extern crate rustc_serialize;
4+
5+
use std::collections::HashMap;
6+
use std::any::{Any, TypeId};
7+
use std::cell::RefCell;
8+
use std::rc::Rc;
9+
use rustc_serialize::Encodable;
10+
use webplatform::{Document, HtmlNode};
11+
12+
pub use mustache::{Template};
13+
14+
15+
pub struct Component<Data> {
16+
template: Template,
17+
data: Data,
18+
}
19+
20+
pub enum EventType {
21+
Click,
22+
}
23+
24+
impl EventType {
25+
fn name(&self) -> &'static str {
26+
match *self {
27+
EventType::Click => "click",
28+
}
29+
}
30+
}
31+
32+
pub struct View<'a, 'doc: 'a, Data: 'a> {
33+
// document and el are redundant of node, but needed for nested queries
34+
document: &'a Document<'doc>,
35+
el: String,
36+
37+
node: Rc<HtmlNode<'doc>>,
38+
component: Rc<RefCell<Component<Data>>>,
39+
}
40+
41+
impl <'a, 'doc: 'a, Data: 'doc + Encodable> View<'a, 'doc, Data> {
42+
pub fn update<F>(&self, f: F) where F: Fn(&mut Data) {
43+
println!("Update called {:?}", self.node);
44+
{
45+
let mut component = self.component.borrow_mut();
46+
f(&mut component.data);
47+
}
48+
self.repaint()
49+
}
50+
51+
pub fn on<F: 'doc>(&self, event: EventType, f: F) where F: Fn(&mut Data) {
52+
println!("On called {:?}", self.node);
53+
{
54+
let mut rc_component = self.component.clone();
55+
let node = self.node.clone();
56+
self.node.on(event.name(), move |evt| {
57+
println!("Event fired for target {:?}", evt.target);
58+
let rendered = {
59+
let mut component = rc_component.borrow_mut();
60+
f(&mut component.data);
61+
component.render()
62+
};
63+
node.html_set(&rendered);
64+
});
65+
println!("On handler registered");
66+
}
67+
}
68+
69+
fn repaint(&self) {
70+
println!("Repaint called {:?}", self.node);
71+
let component = self.component.borrow();
72+
let rendered = component.render();
73+
self.node.html_set(&rendered);
74+
}
75+
76+
}
77+
78+
#[derive(Hash, Eq, PartialEq)]
79+
pub struct ViewId {
80+
tid: TypeId,
81+
selector: String,
82+
}
83+
84+
impl ViewId {
85+
fn new<Data: 'static + Encodable>(el: &str) -> ViewId {
86+
ViewId {
87+
tid: TypeId::of::<Data>(),
88+
selector: el.to_owned(),
89+
}
90+
}
91+
}
92+
93+
pub fn init<'a>() -> WebApp<'a> {
94+
WebApp {
95+
document: webplatform::init(),
96+
views: HashMap::new(),
97+
}
98+
}
99+
100+
pub struct WebApp<'doc> {
101+
document: Document<'doc>,
102+
views: HashMap<ViewId, Box<Any>>,
103+
}
104+
105+
impl <'a> Drop for WebApp<'a> {
106+
fn drop(&mut self) {
107+
webplatform::spin();
108+
}
109+
}
110+
111+
impl <'a, 'doc: 'a> WebApp<'doc> {
112+
pub fn bind<Data: 'static + Encodable>(&'a mut self, el: &str, data: Data) -> View<'a, 'doc, Data> {
113+
let node = self.document.element_query(el).unwrap();
114+
let text = node.html_get();
115+
let template = mustache::compile_str(&text).unwrap();
116+
117+
let view_id = ViewId::new::<Data>(el);
118+
let component = Component {
119+
template: template,
120+
data: data,
121+
};
122+
self.views.insert(view_id, Box::new(Rc::new(RefCell::new(component))));
123+
124+
let view = self.view(el);
125+
view.repaint();
126+
view
127+
}
128+
129+
pub fn view<Data: 'static + Encodable>(&'a self, el: &str) -> View<'a, 'doc, Data> {
130+
let view_id = ViewId::new::<Data>(el);
131+
let entry = self.views.get(&view_id).unwrap();
132+
let component: &Rc<RefCell<Component<Data>>> = entry.downcast_ref().unwrap();
133+
View {
134+
node: Rc::new(self.document.element_query(el).unwrap()),
135+
el: el.to_owned(),
136+
document: &self.document,
137+
component: component.clone(),
138+
}
139+
}
140+
}
141+
142+
impl <Data: Encodable> Component<Data> {
143+
fn new(template: Template, data: Data) -> Component<Data> {
144+
Component {
145+
template: template,
146+
data: data,
147+
}
148+
}
149+
150+
fn render(&self) -> String {
151+
let mut output = Vec::new();
152+
self.template.render(&mut output, &self.data).unwrap();
153+
String::from_utf8_lossy(&output).into_owned()
154+
}
155+
}
156+
157+
#[cfg(test)]
158+
mod tests {
159+
#[test]
160+
fn it_works() {
161+
unimplemented!()
162+
}
163+
}

static/reverser.html

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!doctype html>
2+
<html lang="en-us">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6+
<title>Reverser Example</title>
7+
</head>
8+
<body>
9+
10+
<div id="reverser">
11+
<p>{{ message }}</p>
12+
<button>Reverse Message</button>
13+
</div>
14+
15+
<script async type="text/javascript" src="reverser.js"></script>
16+
</body>
17+
</html>

0 commit comments

Comments
 (0)