use std::rc::Rc;
use std::cell::RefCell;
use stdweb::web::{Element, EventListenerHandle, FileList, INode, Node};
use stdweb::web::html_element::SelectElement;
use virtual_dom::{Listener, VDiff, VNode};
use callback::Callback;
use scheduler::{Runnable, Shared, scheduler};
pub type ShouldRender = bool;
pub trait Component: Sized + 'static {
type Message: 'static;
type Properties: Clone + PartialEq + Default;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self;
fn update(&mut self, msg: Self::Message) -> ShouldRender;
fn change(&mut self, _: Self::Properties) -> ShouldRender {
unimplemented!("you should implement `change` method for a component with properties")
}
fn destroy(&mut self) { }
}
pub trait Renderable<COMP: Component> {
fn view(&self) -> Html<COMP>;
}
pub(crate) enum ComponentUpdate< COMP: Component> {
Create(ComponentLink<COMP>),
Message(COMP::Message),
Properties(COMP::Properties),
Destroy,
}
pub struct ComponentLink<COMP: Component> {
scope: Scope<COMP>,
}
impl<COMP> ComponentLink<COMP>
where
COMP: Component + Renderable<COMP>,
{
fn connect(scope: &Scope<COMP>) -> Self {
ComponentLink {
scope: scope.clone(),
}
}
pub fn send_back<F, IN>(&mut self, function: F) -> Callback<IN>
where
F: Fn(IN) -> COMP::Message + 'static,
{
let scope = self.scope.clone();
let closure = move |input| {
let output = function(input);
scope.clone().send_message(output);
};
closure.into()
}
pub fn send_self(&mut self, msg: COMP::Message) {
self.scope.send_message(msg);
}
}
pub struct Scope<COMP: Component> {
shared_component: Shared<Option<ComponentRunnable<COMP>>>,
}
impl<COMP: Component> Clone for Scope<COMP> {
fn clone(&self) -> Self {
Scope {
shared_component: self.shared_component.clone(),
}
}
}
impl<COMP> Scope<COMP>
where
COMP: Component + Renderable<COMP>,
{
pub(crate) fn send(&mut self, update: ComponentUpdate<COMP>) {
let envelope = ComponentEnvelope {
shared_component: self.shared_component.clone(),
message: Some(update),
};
let runnable: Box<dyn Runnable> = Box::new(envelope);
scheduler().put_and_try_run(runnable);
}
pub fn send_message(&mut self, message: COMP::Message) {
let update = ComponentUpdate::Message(message);
self.send(update);
}
}
pub type NodeCell = Rc<RefCell<Option<Node>>>;
impl<COMP> Scope<COMP>
where
COMP: Component + Renderable<COMP>,
{
pub(crate) fn new() -> Self {
let shared_component = Rc::new(RefCell::new(None));
Scope { shared_component }
}
pub(crate) fn mount_in_place(
self,
element: Element,
ancestor: Option<VNode<COMP>>,
occupied: Option<NodeCell>,
init_props: Option<COMP::Properties>,
) -> Scope<COMP> {
let runnable = ComponentRunnable {
env: self.clone(),
component: None,
last_frame: None,
element,
ancestor,
occupied,
init_props,
destroyed: false,
};
let mut scope = self.clone();
*scope.shared_component.borrow_mut() = Some(runnable);
let link = ComponentLink::connect(&scope);
scope.send(ComponentUpdate::Create(link));
scope
}
}
struct ComponentRunnable<COMP: Component> {
env: Scope<COMP>,
component: Option<COMP>,
last_frame: Option<VNode<COMP>>,
element: Element,
ancestor: Option<VNode<COMP>>,
occupied: Option<NodeCell>,
init_props: Option<COMP::Properties>,
destroyed: bool,
}
struct ComponentEnvelope<COMP>
where
COMP: Component,
{
shared_component: Shared<Option<ComponentRunnable<COMP>>>,
message: Option<ComponentUpdate<COMP>>,
}
impl<COMP> Runnable for ComponentEnvelope<COMP>
where
COMP: Component + Renderable<COMP>,
{
fn run(&mut self) {
let mut component = self.shared_component.borrow_mut();
let this = component.as_mut().expect("shared component not set");
if this.destroyed {
return;
}
let mut should_update = false;
let upd = self.message.take().expect("component's envelope called twice");
let env = this.env.clone();
match upd {
ComponentUpdate::Create(link) => {
let props = this.init_props.take().unwrap_or_default();
this.component = Some(COMP::create(props, link));
let current_frame = this.component.as_ref().unwrap().view();
this.last_frame = Some(current_frame);
let node = this.last_frame.as_mut()
.unwrap()
.apply(this.element.as_node(), None, this.ancestor.take(), &env);
if let Some(ref mut cell) = this.occupied {
*cell.borrow_mut() = node;
}
}
ComponentUpdate::Message(msg) => {
should_update |= this.component.as_mut()
.expect("component was not created to process messages")
.update(msg);
}
ComponentUpdate::Properties(props) => {
should_update |= this.component.as_mut()
.expect("component was not created to process properties")
.change(props);
}
ComponentUpdate::Destroy => {
this.component.as_mut().unwrap().destroy();
this.destroyed = true;
}
}
if should_update {
let mut next_frame = this.component.as_ref().unwrap().view();
let node =
next_frame.apply(this.element.as_node(), None, this.last_frame.take(), &env);
if let Some(ref mut cell) = this.occupied {
*cell.borrow_mut() = node;
}
this.last_frame = Some(next_frame);
}
}
}
pub type Html<MSG> = VNode<MSG>;
macro_rules! impl_action {
($($action:ident($event:ident : $type:ident) -> $ret:ty => $convert:expr)*) => {$(
pub mod $action {
use stdweb::web::{IEventTarget, Element};
use stdweb::web::event::{IEvent, $type};
use super::*;
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, COMP> Listener<COMP> for Wrapper<T>
where
T: Fn($ret) -> COMP::Message + 'static,
COMP: Component + Renderable<COMP>,
{
fn kind(&self) -> &'static str {
stringify!($action)
}
fn attach(&mut self, element: &Element, mut activator: Scope<COMP>)
-> 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);
activator.send_message(msg);
};
element.add_event_listener(listener)
}
}
}
)*};
}
impl_action! {
onclick(event: ClickEvent) -> ClickEvent => |_, event| { event }
ondoubleclick(event: DoubleClickEvent) -> DoubleClickEvent => |_, event| { event }
onkeypress(event: KeyPressEvent) -> KeyPressEvent => |_, event| { event }
onkeydown(event: KeyDownEvent) -> KeyDownEvent => |_, event| { event }
onkeyup(event: KeyUpEvent) -> KeyUpEvent => |_, event| { event }
onmousemove(event: MouseMoveEvent) -> MouseMoveEvent => |_, event| { event }
onmousedown(event: MouseDownEvent) -> MouseDownEvent => |_, event| { event }
onmouseup(event: MouseUpEvent) -> MouseUpEvent => |_, event| { event }
onmouseover(event: MouseOverEvent) -> MouseOverEvent => |_, event| { event }
onmouseout(event: MouseOutEvent) -> MouseOutEvent => |_, event| { event }
onmouseenter(event: MouseEnterEvent) -> MouseEnterEvent => |_, event| { event }
onmouseleave(event: MouseLeaveEvent) -> MouseLeaveEvent => |_, event| { event }
onmousewheel(event: MouseWheelEvent) -> MouseWheelEvent => |_, event| { event }
ongotpointercapture(event: GotPointerCaptureEvent) -> GotPointerCaptureEvent => |_, event| { event }
onlostpointercapture(event: LostPointerCaptureEvent) -> LostPointerCaptureEvent => |_, event| { event }
onpointercancel(event: PointerCancelEvent) -> PointerCancelEvent => |_, event| { event }
onpointerdown(event: PointerDownEvent) -> PointerDownEvent => |_, event| { event }
onpointerenter(event: PointerEnterEvent) -> PointerEnterEvent => |_, event| { event }
onpointerleave(event: PointerLeaveEvent) -> PointerLeaveEvent => |_, event| { event }
onpointermove(event: PointerMoveEvent) -> PointerMoveEvent => |_, event| { event }
onpointerout(event: PointerOutEvent) -> PointerOutEvent => |_, event| { event }
onpointerover(event: PointerOverEvent) -> PointerOverEvent => |_, event| { event }
onpointerup(event: PointerUpEvent) -> PointerUpEvent => |_, event| { event }
onscroll(event: ScrollEvent) -> ScrollEvent => |_, event| { event }
onblur(event: BlurEvent) -> BlurEvent => |_, event| { event }
onfocus(event: FocusEvent) -> FocusEvent => |_, event| { event }
onsubmit(event: SubmitEvent) -> SubmitEvent => |_, event| { event }
ondragstart(event: DragStartEvent) -> DragStartEvent => |_, event| { event }
ondrag(event: DragEvent) -> DragEvent => |_, event| { event }
ondragend(event: DragEndEvent) -> DragEndEvent => |_, event| { event }
ondragenter(event: DragEnterEvent) -> DragEnterEvent => |_, event| { event }
ondragleave(event: DragLeaveEvent) -> DragLeaveEvent => |_, event| { event }
ondragover(event: DragOverEvent) -> DragOverEvent => |_, event| { event }
ondragexit(event: DragExitEvent) -> DragExitEvent => |_, event| { event }
ondrop(event: DragDropEvent) -> DragDropEvent => |_, event| { event }
oncontextmenu(event: ContextMenuEvent) -> ContextMenuEvent => |_, event| { event }
oninput(event: InputEvent) -> InputData => |this: &Element, _| {
use stdweb::web::html_element::{InputElement, TextAreaElement};
use stdweb::unstable::TryInto;
let value = match this.clone().try_into() {
Ok(input) => {
let input: InputElement = input;
input.raw_value()
}
Err(_e) => {
match this.clone().try_into() {
Ok(tae) => {
let tae: TextAreaElement = tae;
tae.value()
}
Err(_e) => {
panic!("only an InputElement or TextAreaElement can have an oninput event listener");
}
}
}
};
InputData { value }
}
onchange(event: ChangeEvent) -> ChangeData => |this: &Element, _| {
use stdweb::web::{FileList, IElement};
use stdweb::web::html_element::{InputElement, TextAreaElement, SelectElement};
use stdweb::unstable::TryInto;
match this.node_name().as_ref() {
"INPUT" => {
let input: InputElement = this.clone().try_into().unwrap();
let is_file = input.get_attribute("type").map(|value| {
value.eq_ignore_ascii_case("file")
})
.unwrap_or(false);
if is_file {
let files: FileList = js!( return @{input}.files; )
.try_into()
.unwrap();
ChangeData::Files(files)
} else {
ChangeData::Value(input.raw_value())
}
}
"TEXTAREA" => {
let tae: TextAreaElement = this.clone().try_into().unwrap();
ChangeData::Value(tae.value())
}
"SELECT" => {
let se: SelectElement = this.clone().try_into().unwrap();
ChangeData::Select(se)
}
_ => {
panic!("only an InputElement, TextAreaElement or SelectElement can have an onchange event listener");
}
}
}
}
#[derive(Debug)]
pub struct InputData {
pub value: String,
}
#[derive(Debug)]
pub enum ChangeData {
Value(String),
Select(SelectElement),
Files(FileList),
}
#[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()
}
}