use std::sync::mpsc::{Sender, Receiver, channel};
use stdweb::Value;
use stdweb::web::{Element, INode, EventListenerHandle, document};
use stdweb::web::event::{IMouseEvent, IKeyboardEvent};
use virtual_dom::{VNode, VTag, Listener};
fn clear_element(element: &Element) {
while let Some(child) = element.last_child() {
element.remove_child(&child).expect("can't remove a child");
}
}
pub struct AppSender<MSG> {
tx: Sender<MSG>,
bind: Value,
}
impl<MSG> Clone for AppSender<MSG> {
fn clone(&self) -> Self {
AppSender {
tx: self.tx.clone(),
bind: self.bind.clone(),
}
}
}
impl<MSG> AppSender<MSG> {
pub fn send(&mut self, msg: MSG) {
self.tx.send(msg).expect("App lost the receiver!");
let bind = &self.bind;
js! {
var bind = @{bind};
setTimeout(bind.loop);
}
}
}
pub struct App<MSG> {
tx: Sender<MSG>,
rx: Option<Receiver<MSG>>,
bind: Value,
}
impl<MSG: 'static> App<MSG> {
pub fn new() -> Self {
let bind = js! {
return { "loop": function() { } };
};
let (tx, rx) = channel();
App {
tx,
rx: Some(rx),
bind,
}
}
pub fn sender(&mut self) -> AppSender<MSG> {
AppSender {
tx: self.tx.clone(),
bind: self.bind.clone(),
}
}
pub fn mount<CTX, MOD, U, V>(&mut self, context: CTX, model: MOD, update: U, view: V)
where
CTX: 'static,
MOD: 'static,
U: Fn(&mut CTX, &mut MOD, MSG) + 'static,
V: Fn(&MOD) -> Html<MSG> + 'static,
{
self.mount_to("body", context, model, update, view)
}
pub fn mount_to<CTX, MOD, U, V>(&mut self, selector: &str, mut context: CTX, mut model: MOD, update: U, view: V)
where
CTX: 'static,
MOD: 'static,
U: Fn(&mut CTX, &mut MOD, MSG) + 'static,
V: Fn(&MOD) -> Html<MSG> + 'static,
{
let element = document().query_selector(selector)
.expect(format!("can't get node with selector `{}` for rendering", selector).as_str());
clear_element(&element);
let mut messages = Vec::new();
let mut last_frame = VNode::from(view(&model));
last_frame.apply(&element, None, self.sender());
let mut last_frame = Some(last_frame);
let rx = self.rx.take().expect("application runned without a receiver");
let bind = self.bind.clone();
let sender = self.sender();
let mut callback = move || {
messages.extend(rx.try_iter());
for msg in messages.drain(..) {
update(&mut context, &mut model, msg);
}
let mut next_frame = VNode::from(view(&model));
next_frame.apply(&element, last_frame.take(), sender.clone());
last_frame = Some(next_frame);
};
callback();
js! {
var bind = @{bind};
var callback = @{callback};
bind.loop = callback;
}
}
}
pub type Html<MSG> = VTag<MSG>;
macro_rules! impl_action {
($($action:ident($event:ident : $type:ident) -> $ret:ty => $convert:expr)*) => {$(
/// An abstract implementation of a listener.
pub mod $action {
use stdweb::web::{IEventTarget, Element};
use stdweb::web::event::{IEvent, $type};
use super::*;
/// A wrapper for a callback.
/// Listener extracted from here when attached.
pub struct Wrapper<F>(Option<F>);
pub type Event = $ret;
impl<F, MSG> From<F> for Wrapper<F>
where
MSG: 'static,
F: Fn($ret) -> MSG + 'static,
{
fn from(handler: F) -> Self {
Wrapper(Some(handler))
}
}
impl<T, MSG> Listener<MSG> for Wrapper<T>
where
MSG: 'static,
T: Fn($ret) -> MSG + 'static,
{
fn kind(&self) -> &'static str {
stringify!($action)
}
fn attach(&mut self, element: &Element, mut sender: AppSender<MSG>)
-> EventListenerHandle {
let handler = self.0.take().expect("tried to attach listener twice");
let this = element.clone();
let listener = move |event: $type| {
debug!("Event handler: {}", stringify!($type));
event.stop_propagation();
let handy_event: $ret = $convert(&this, event);
let msg = handler(handy_event);
sender.send(msg);
};
element.add_event_listener(listener)
}
}
}
)*};
}
impl_action! {
onclick(event: ClickEvent) -> MouseData => |_, event| { MouseData::from(event) }
ondoubleclick(event: DoubleClickEvent) -> MouseData => |_, event| { MouseData::from(event) }
onkeypress(event: KeypressEvent) -> KeyData => |_, event| { KeyData::from(event) }
oninput(event: InputEvent) -> InputData => |this: &Element, _| {
use stdweb::web::html_element::InputElement;
use stdweb::unstable::TryInto;
let input: InputElement = this.clone().try_into().expect("only an InputElement can have an oninput event listener");
let value = input.value().into_string().unwrap_or_else(|| "".into());
InputData { value }
}
}
#[derive(Debug)]
pub struct MouseData {
pub screen_x: f64,
pub screen_y: f64,
pub client_x: f64,
pub client_y: f64,
}
impl<T: IMouseEvent> From<T> for MouseData {
fn from(event: T) -> Self {
MouseData {
screen_x: event.screen_x(),
screen_y: event.screen_y(),
client_x: event.client_x(),
client_y: event.client_y(),
}
}
}
#[derive(Debug)]
pub struct InputData {
pub value: String,
}
#[derive(Debug)]
pub struct KeyData {
pub key: String,
}
impl<T: IKeyboardEvent> From<T> for KeyData {
fn from(event: T) -> Self {
KeyData { key: event.key() }
}
}
#[derive(Debug)]
pub struct Href {
link: String,
}
impl From<String> for Href {
fn from(link: String) -> Self {
Href { link }
}
}
impl<'a> From<&'a str> for Href {
fn from(link: &'a str) -> Self {
Href { link: link.to_owned() }
}
}
impl ToString for Href {
fn to_string(&self) -> String {
self.link.to_owned()
}
}