From d79d96fa1bc752aa53f724ed916739bf361b1934 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Tue, 19 Dec 2017 21:58:25 +0300 Subject: [PATCH 01/14] Add empty diff function --- src/html.rs | 69 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/src/html.rs b/src/html.rs index 44c113702e2..d392d0ff828 100644 --- a/src/html.rs +++ b/src/html.rs @@ -17,6 +17,42 @@ use stdweb::web::event::{IMouseEvent, IKeyboardEvent}; use stdweb::web::html_element::InputElement; use stdweb::unstable::TryInto; +// Diff updates text only! + +type Ref<'a, MSG> = &'a Child; + +fn diff_node<'a, MSG>( + list: &mut Vec>, + _a: Ref<'a, MSG>, + _b: Ref<'a, MSG>) { +} + +// Index of Vec is an index of patch +fn diff<'a, MSG>(a: Ref<'a, MSG>, b: Ref<'a, MSG>) -> Vec> { + let mut list = Vec::new(); + let mut patch = Patch::new(&a); + diff_node(&mut list, a, b); + list +} + +struct Patch<'a, MSG: 'a> { + nref: Ref<'a, MSG>, + actions: Vec, +} + +impl<'a, MSG: 'a> Patch<'a, MSG> { + fn new(nref: Ref<'a, MSG>) -> Self { + let actions = Vec::new(); + Patch { nref, actions } + } +} + +enum Action { + PutNode, + SetValue(String), + SetAttribute(String, String), +} + pub fn program(mut model: M, update: U, view: V) where M: 'static, @@ -27,17 +63,24 @@ where stdweb::initialize(); // No messages at start let messages = Rc::new(RefCell::new(Vec::new())); + let body = document().query_selector("body").unwrap(); + let mut onscreen_scene = Child::from(view(&model)); + onscreen_scene.render(messages.clone(), &body); + let mut callback = move || { let mut borrowed = messages.borrow_mut(); for msg in borrowed.drain(..) { update(&mut model, msg); } - let html = view(&model); - let body = document().query_selector("body").unwrap(); + let offscreen_scene = Child::from(view(&model)); + { + let _patch = diff(&onscreen_scene, &offscreen_scene); + } + onscreen_scene = offscreen_scene; while body.has_child_nodes() { body.remove_child(&body.last_child().unwrap()).unwrap(); } - html.render(messages.clone(), &body); + onscreen_scene.render(messages.clone(), &body); }; // Initial call for first rendering callback(); @@ -69,7 +112,7 @@ type Attributes = HashMap<&'static str, String>; type Classes = Vec<&'static str>; trait Render { - fn render(self, messages: Messages, element: &Element); + fn render(&mut self, messages: Messages, element: &Element); } pub enum Child { @@ -95,10 +138,10 @@ impl fmt::Debug for Child { impl Render for Child { - fn render(self, messages: Messages, element: &Element) { - match self { - Child::VNode(vnode) => vnode.render(messages, element), - Child::VText(vtext) => vtext.render(messages, element), + fn render(&mut self, messages: Messages, element: &Element) { + match *self { + Child::VNode(ref mut vnode) => vnode.render(messages, element), + Child::VText(ref mut vtext) => vtext.render(messages, element), } } } @@ -132,7 +175,7 @@ impl VText { } impl Render for VText { - fn render(self, _: Messages, element: &Element) { + fn render(&mut self, _: Messages, element: &Element) { let child_element = document().create_text_node(&self.text); element.append_child(&child_element); } @@ -192,7 +235,7 @@ impl VNode { } impl Render for VNode { - fn render(mut self, messages: Messages, element: &Element) { + fn render(&mut self, messages: Messages, element: &Element) { let child_element = document().create_element(self.tag); let child_element = { let cloned: Result = child_element.clone().try_into(); @@ -207,16 +250,16 @@ impl Render for VNode { child_element } }; - for (name, value) in self.attributes { + for (name, value) in self.attributes.iter() { set_attribute(&child_element, name, &value); } - for class in self.classes { + for class in self.classes.iter() { child_element.class_list().add(&class); } for mut listener in self.listeners.drain(..) { listener.attach(&child_element, messages.clone()); } - for child in self.childs.drain(..) { + for mut child in self.childs.drain(..) { child.render(messages.clone(), &child_element); } element.append_child(&child_element); From 4375eb168f39f185797743b0d4b00ee0c0f1bfbb Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Wed, 20 Dec 2017 00:38:46 +0300 Subject: [PATCH 02/14] Add draft implementation of live updates --- src/html.rs | 179 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 133 insertions(+), 46 deletions(-) diff --git a/src/html.rs b/src/html.rs index d392d0ff828..3b6669824ad 100644 --- a/src/html.rs +++ b/src/html.rs @@ -16,42 +16,64 @@ use stdweb::web::{INode, IElement, Element, document}; use stdweb::web::event::{IMouseEvent, IKeyboardEvent}; use stdweb::web::html_element::InputElement; use stdweb::unstable::TryInto; +use stdweb::unstable::TryFrom; // Diff updates text only! - +/* type Ref<'a, MSG> = &'a Child; fn diff_node<'a, MSG>( - list: &mut Vec>, - _a: Ref<'a, MSG>, - _b: Ref<'a, MSG>) { + script: &mut Script<'a, MSG>, + a: Option>, + b: Option>) { + // Compare nodes + match (a, b) { + (Some(&Child::VNode(ref left)), Some(&Child::VNode(ref right))) => { + // Compare values + match (&left.value, &right.value) { + (&Some(ref left), &Some(ref right)) => { + // Set new value if changed + if left != right { + script.push(Action::SetValue(left)); + } + } + _ => { + unimplemented!(); + } + } + } + (Some(_), None) => { + // Construct new node + } + (None, None) => { + // No nodes no changes + } + _ => { + unimplemented!(); + } + } } // Index of Vec is an index of patch -fn diff<'a, MSG>(a: Ref<'a, MSG>, b: Ref<'a, MSG>) -> Vec> { - let mut list = Vec::new(); - let mut patch = Patch::new(&a); - diff_node(&mut list, a, b); - list -} - -struct Patch<'a, MSG: 'a> { - nref: Ref<'a, MSG>, - actions: Vec, +fn diff<'a, MSG>(a: Ref<'a, MSG>, b: Ref<'a, MSG>) -> Script<'a, MSG> { + let mut script = Vec::new(); + diff_node(&mut script, a, b); + script } -impl<'a, MSG: 'a> Patch<'a, MSG> { - fn new(nref: Ref<'a, MSG>) -> Self { - let actions = Vec::new(); - Patch { nref, actions } - } -} +type Script<'a, MSG> = Vec>; -enum Action { - PutNode, - SetValue(String), +/// I use script approach because will give good perfomance when threads +/// will appear in WASM and I can send actions through channels. +enum Action<'a, MSG: 'a> { + StepInto, + StepOut, + CreateNode(Ref<'a, MSG>), + SetValue(&'a str), SetAttribute(String, String), + RemoveAttribute(String), } +*/ pub fn program(mut model: M, update: U, view: V) where @@ -61,26 +83,21 @@ where V: Fn(&M) -> Html + 'static, { stdweb::initialize(); + let body = document().query_selector("body").unwrap(); + while body.has_child_nodes() { + body.remove_child(&body.last_child().unwrap()).unwrap(); + } + let app = document().create_element("app"); + body.append_child(&app); // No messages at start let messages = Rc::new(RefCell::new(Vec::new())); - let body = document().query_selector("body").unwrap(); - let mut onscreen_scene = Child::from(view(&model)); - onscreen_scene.render(messages.clone(), &body); - let mut callback = move || { let mut borrowed = messages.borrow_mut(); for msg in borrowed.drain(..) { update(&mut model, msg); } - let offscreen_scene = Child::from(view(&model)); - { - let _patch = diff(&onscreen_scene, &offscreen_scene); - } - onscreen_scene = offscreen_scene; - while body.has_child_nodes() { - body.remove_child(&body.last_child().unwrap()).unwrap(); - } - onscreen_scene.render(messages.clone(), &body); + let mut html = view(&model); + html.render(&body, Some(&app), messages.clone()); }; // Initial call for first rendering callback(); @@ -112,7 +129,7 @@ type Attributes = HashMap<&'static str, String>; type Classes = Vec<&'static str>; trait Render { - fn render(&mut self, messages: Messages, element: &Element); + fn render(&mut self, parent: &Element, this: Option<&Element>, messages: Messages); } pub enum Child { @@ -138,10 +155,10 @@ impl fmt::Debug for Child { impl Render for Child { - fn render(&mut self, messages: Messages, element: &Element) { + fn render(&mut self, parent: &Element, this: Option<&Element>, messages: Messages) { match *self { - Child::VNode(ref mut vnode) => vnode.render(messages, element), - Child::VText(ref mut vtext) => vtext.render(messages, element), + Child::VNode(ref mut vnode) => vnode.render(parent, this, messages), + Child::VText(ref mut vtext) => vtext.render(parent, this, messages), } } } @@ -175,9 +192,13 @@ impl VText { } impl Render for VText { - fn render(&mut self, _: Messages, element: &Element) { - let child_element = document().create_text_node(&self.text); - element.append_child(&child_element); + fn render(&mut self, parent: &Element, this: Option<&Element>, _: Messages) { + if let Some(this) = this { + // Check node type and replace if wrong + } else { + let element = document().create_text_node(&self.text); + parent.append_child(&element); + } } } @@ -232,11 +253,76 @@ impl VNode { pub fn add_listener(&mut self, listener: Box>) { self.listeners.push(listener); } + + fn fill_node(&mut self, this: &Element, messages: Messages) { + let mut childs = self.childs.drain(..).map(Some).collect::>(); + let mut nodes = this.child_nodes().iter().map(Some).collect::>(); + let diff = childs.len() as i32 - nodes.len() as i32; + if diff > 0 { + for _ in 0..diff { + nodes.push(None); + } + } else if diff < 0 { + for _ in 0..-diff { + childs.push(None); + } + } + + for pair in childs.into_iter().zip(nodes) { + match pair { + (Some(mut child), Some(node)) => { + let element = Element::try_from(node).unwrap(); + // Check the tag + child.render(this, Some(&element), messages.clone()); + } + (Some(mut child), None) => { + // Append a new one + child.render(this, None, messages.clone()); + } + (None, Some(node)) => { + this.remove_child(&node); + // Remove redundant node + } + (None, None) => { + panic!("both nodes are not existent during comparsion"); + } + } + } + } } impl Render for VNode { - fn render(&mut self, messages: Messages, element: &Element) { - let child_element = document().create_element(self.tag); + fn render(&mut self, parent: &Element, this: Option<&Element>, messages: Messages) { + if let Some(this) = this { + if self.tag != this.node_name() { + let element = document().create_element(self.tag); + parent.replace_child(&element, this); + self.fill_node(&element, messages.clone()); + } else { + self.fill_node(this, messages.clone()); + } + } else { + let element = document().create_element(self.tag); + parent.append_child(&element); + self.fill_node(&element, messages.clone()); + } + /* + let child_element = { + if this.node_name() != self.tag { + let new_element = document().create_element(self.tag); + this.parent_node().unwrap().replace_child(&new_element, this); + new_element + } else { + this + } + }; + */ + /* + for mut child in { + child.render(&child_element, messages.clone()); + } + */ + /* let child_element = { let cloned: Result = child_element.clone().try_into(); if let &Some(ref value) = &self.value { @@ -260,9 +346,10 @@ impl Render for VNode { listener.attach(&child_element, messages.clone()); } for mut child in self.childs.drain(..) { - child.render(messages.clone(), &child_element); + child.render(&child_element, messages.clone()); } element.append_child(&child_element); + */ } } From 77d98420320e49897bd12f554ecd0b9e5971dbee Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Thu, 21 Dec 2017 00:46:27 +0300 Subject: [PATCH 03/14] Add recursive diff updates But listerers duplicaates and should be fixed. --- src/html.rs | 196 +++++++++++++++++--------------------------------- src/macros.rs | 21 ++++-- 2 files changed, 81 insertions(+), 136 deletions(-) diff --git a/src/html.rs b/src/html.rs index 3b6669824ad..24dfc3331e2 100644 --- a/src/html.rs +++ b/src/html.rs @@ -3,77 +3,12 @@ use std::rc::Rc; use std::cell::RefCell; use std::collections::HashMap; -/* -pub trait Message {} - -impl Fn(T) -> Self for Message { -} -*/ - use stdweb; -use stdweb::web::{INode, IElement, Element, document}; +use stdweb::web::{INode, IElement, Node, Element, document}; use stdweb::web::event::{IMouseEvent, IKeyboardEvent}; use stdweb::web::html_element::InputElement; use stdweb::unstable::TryInto; -use stdweb::unstable::TryFrom; - -// Diff updates text only! -/* -type Ref<'a, MSG> = &'a Child; - -fn diff_node<'a, MSG>( - script: &mut Script<'a, MSG>, - a: Option>, - b: Option>) { - // Compare nodes - match (a, b) { - (Some(&Child::VNode(ref left)), Some(&Child::VNode(ref right))) => { - // Compare values - match (&left.value, &right.value) { - (&Some(ref left), &Some(ref right)) => { - // Set new value if changed - if left != right { - script.push(Action::SetValue(left)); - } - } - _ => { - unimplemented!(); - } - } - } - (Some(_), None) => { - // Construct new node - } - (None, None) => { - // No nodes no changes - } - _ => { - unimplemented!(); - } - } -} - -// Index of Vec is an index of patch -fn diff<'a, MSG>(a: Ref<'a, MSG>, b: Ref<'a, MSG>) -> Script<'a, MSG> { - let mut script = Vec::new(); - diff_node(&mut script, a, b); - script -} - -type Script<'a, MSG> = Vec>; - -/// I use script approach because will give good perfomance when threads -/// will appear in WASM and I can send actions through channels. -enum Action<'a, MSG: 'a> { - StepInto, - StepOut, - CreateNode(Ref<'a, MSG>), - SetValue(&'a str), - SetAttribute(String, String), - RemoveAttribute(String), -} -*/ pub fn program(mut model: M, update: U, view: V) where @@ -92,12 +27,15 @@ where // No messages at start let messages = Rc::new(RefCell::new(Vec::new())); let mut callback = move || { + println!("Yew Loop Callback"); let mut borrowed = messages.borrow_mut(); for msg in borrowed.drain(..) { update(&mut model, msg); } let mut html = view(&model); - html.render(&body, Some(&app), messages.clone()); + println!("Do render"); + let this = body.first_child(); + html.render(&body, this, messages.clone()); }; // Initial call for first rendering callback(); @@ -129,7 +67,7 @@ type Attributes = HashMap<&'static str, String>; type Classes = Vec<&'static str>; trait Render { - fn render(&mut self, parent: &Element, this: Option<&Element>, messages: Messages); + fn render(&mut self, parent: &T, this: Option, messages: Messages); } pub enum Child { @@ -155,7 +93,7 @@ impl fmt::Debug for Child { impl Render for Child { - fn render(&mut self, parent: &Element, this: Option<&Element>, messages: Messages) { + fn render(&mut self, parent: &T, this: Option, messages: Messages) { match *self { Child::VNode(ref mut vnode) => vnode.render(parent, this, messages), Child::VText(ref mut vtext) => vtext.render(parent, this, messages), @@ -192,8 +130,9 @@ impl VText { } impl Render for VText { - fn render(&mut self, parent: &Element, this: Option<&Element>, _: Messages) { - if let Some(this) = this { + fn render(&mut self, parent: &T, this: Option, _: Messages) { + println!("Render text node!"); + if let Some(_) = this { // Check node type and replace if wrong } else { let element = document().create_text_node(&self.text); @@ -202,7 +141,6 @@ impl Render for VText { } } - pub struct VNode { tag: &'static str, listeners: Listeners, @@ -210,6 +148,7 @@ pub struct VNode { childs: Vec>, classes: Classes, value: Option, + kind: Option, } impl fmt::Debug for VNode { @@ -227,6 +166,7 @@ impl VNode { listeners: Vec::new(), childs: Vec::new(), value: None, + kind: None, } } @@ -246,6 +186,10 @@ impl VNode { self.value = Some(value.to_string()); } + pub fn set_kind(&mut self, value: T) { + self.kind = Some(value.to_string()); + } + pub fn add_attribute(&mut self, name: &'static str, value: T) { self.attributes.insert(name, value.to_string()); } @@ -255,6 +199,41 @@ impl VNode { } fn fill_node(&mut self, this: &Element, messages: Messages) { + println!("Fill VNode"); + let input: Result = this.clone().try_into(); + if let Ok(input) = input { + let old_value = input.value().into_string().unwrap(); + let new_value = self.value.take().unwrap_or_else(String::new); + println!("Value check: {} is? {}", old_value, new_value); + if old_value != new_value { + input.set_value(new_value); + } + if let Some(ref kind) = self.kind { + input.set_kind(kind); + } else { + input.set_kind(""); + } + } + + println!("VNode classes"); + for class in self.classes.iter() { + this.class_list().add(&class); + } + + println!("VNode attributes"); + for (name, value) in self.attributes.iter() { + set_attribute(&this, name, &value); + } + + println!("VNode listeners"); + // TODO IMPORTANT! IT DUPLICATES ALL LISTENERS! + // How to fix? What about to use "global" list of + // listeners mapping by dom references. + for mut listener in self.listeners.drain(..) { + listener.attach(&this, messages.clone()); + } + + println!("VNode children"); let mut childs = self.childs.drain(..).map(Some).collect::>(); let mut nodes = this.child_nodes().iter().map(Some).collect::>(); let diff = childs.len() as i32 - nodes.len() as i32; @@ -270,17 +249,11 @@ impl VNode { for pair in childs.into_iter().zip(nodes) { match pair { - (Some(mut child), Some(node)) => { - let element = Element::try_from(node).unwrap(); - // Check the tag - child.render(this, Some(&element), messages.clone()); - } - (Some(mut child), None) => { - // Append a new one - child.render(this, None, messages.clone()); + (Some(mut child), node) => { + child.render(this, node, messages.clone()); } (None, Some(node)) => { - this.remove_child(&node); + this.remove_child(&node).unwrap(); // Remove redundant node } (None, None) => { @@ -292,64 +265,26 @@ impl VNode { } impl Render for VNode { - fn render(&mut self, parent: &Element, this: Option<&Element>, messages: Messages) { + fn render(&mut self, parent: &T, this: Option, messages: Messages) { + println!("Render: {:?}", this); if let Some(this) = this { - if self.tag != this.node_name() { + println!("Node: {:?}", this.node_name()); + // Important! HTML Expected! + if self.tag.to_owned().to_uppercase() == this.node_name() { + let this = this.try_into().unwrap(); + self.fill_node(&this, messages.clone()); + } else { + println!("REPLACE!"); let element = document().create_element(self.tag); - parent.replace_child(&element, this); + parent.replace_child(&element, &this); + println!("REPLACE DONE!"); self.fill_node(&element, messages.clone()); - } else { - self.fill_node(this, messages.clone()); } } else { let element = document().create_element(self.tag); parent.append_child(&element); self.fill_node(&element, messages.clone()); } - /* - let child_element = { - if this.node_name() != self.tag { - let new_element = document().create_element(self.tag); - this.parent_node().unwrap().replace_child(&new_element, this); - new_element - } else { - this - } - }; - */ - /* - for mut child in { - child.render(&child_element, messages.clone()); - } - */ - /* - let child_element = { - let cloned: Result = child_element.clone().try_into(); - if let &Some(ref value) = &self.value { - if let Ok(input_element) = cloned { - input_element.set_value(value); - input_element.into() - } else { - child_element - } - } else { - child_element - } - }; - for (name, value) in self.attributes.iter() { - set_attribute(&child_element, name, &value); - } - for class in self.classes.iter() { - child_element.class_list().add(&class); - } - for mut listener in self.listeners.drain(..) { - listener.attach(&child_element, messages.clone()); - } - for mut child in self.childs.drain(..) { - child.render(&child_element, messages.clone()); - } - element.append_child(&child_element); - */ } } @@ -387,6 +322,7 @@ macro_rules! impl_action { let handler = self.0.take().unwrap(); let this = element.clone(); let sender = move |event: $type| { + println!("Event handler: {}", stringify!($type)); event.stop_propagation(); let handy_event: $ret = $convert(&this, event); let msg = handler(handy_event); diff --git a/src/macros.rs b/src/macros.rs index 367c2318b24..bb0a9400e25 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -18,6 +18,12 @@ macro_rules! html_impl { $crate::macros::set_value(&mut $stack, $value); html_impl! { $stack ($($tail)*) } }; + // PATTERN: attribute=value, - workaround for `type` attribute + // because `type` is a keyword in Rust + ($stack:ident (type = $kind:expr, $($tail:tt)*)) => { + $crate::macros::set_kind(&mut $stack, $kind); + html_impl! { $stack ($($tail)*) } + }; // Events: ($stack:ident (onclick = $handler:expr, $($tail:tt)*)) => { html_impl! { $stack ((onclick) = $handler, $($tail)*) } @@ -39,12 +45,6 @@ macro_rules! html_impl { $crate::macros::attach_listener(&mut $stack, Box::new(listener)); html_impl! { $stack ($($tail)*) } }; - // PATTERN: attribute=value, - workaround for `type` attribute - // because `type` is a keyword in Rust - ($stack:ident (type = $val:expr, $($tail:tt)*)) => { - $crate::macros::add_attribute(&mut $stack, "type", $val); - html_impl! { $stack ($($tail)*) } - }; ($stack:ident ($attr:ident = $val:expr, $($tail:tt)*)) => { $crate::macros::add_attribute(&mut $stack, stringify!($attr), $val); html_impl! { $stack ($($tail)*) } @@ -112,6 +112,15 @@ pub fn set_value(stack: &mut Stack, value: &T) { } } +#[doc(hidden)] +pub fn set_kind(stack: &mut Stack, value: T) { + if let Some(node) = stack.last_mut() { + node.set_kind(value); + } else { + panic!("no tag to set type: {}", value.to_string()); + } +} + #[doc(hidden)] pub fn add_attribute(stack: &mut Stack, name: &'static str, value: T) { if let Some(node) = stack.last_mut() { From 3710973f93cf38f525fa828ea84e038afd5b3117 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Fri, 22 Dec 2017 23:26:33 +0300 Subject: [PATCH 04/14] Add debug and warn macros --- src/html.rs | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/html.rs b/src/html.rs index 24dfc3331e2..b918fdc9153 100644 --- a/src/html.rs +++ b/src/html.rs @@ -10,6 +10,20 @@ use stdweb::web::event::{IMouseEvent, IKeyboardEvent}; use stdweb::web::html_element::InputElement; use stdweb::unstable::TryInto; +macro_rules! debug { + ($($e:expr),*) => { + if cfg!(debug) { + println!($($e,)*); + } + }; +} + +macro_rules! warn { + ($($e:expr),*) => { + eprintln!($($e,)*); + }; +} + pub fn program(mut model: M, update: U, view: V) where M: 'static, @@ -27,13 +41,13 @@ where // No messages at start let messages = Rc::new(RefCell::new(Vec::new())); let mut callback = move || { - println!("Yew Loop Callback"); + debug!("Yew Loop Callback"); let mut borrowed = messages.borrow_mut(); for msg in borrowed.drain(..) { update(&mut model, msg); } let mut html = view(&model); - println!("Do render"); + debug!("Do render"); let this = body.first_child(); html.render(&body, this, messages.clone()); }; @@ -131,7 +145,7 @@ impl VText { impl Render for VText { fn render(&mut self, parent: &T, this: Option, _: Messages) { - println!("Render text node!"); + debug!("Render text node!"); if let Some(_) = this { // Check node type and replace if wrong } else { @@ -199,12 +213,12 @@ impl VNode { } fn fill_node(&mut self, this: &Element, messages: Messages) { - println!("Fill VNode"); + debug!("Fill VNode"); let input: Result = this.clone().try_into(); if let Ok(input) = input { let old_value = input.value().into_string().unwrap(); let new_value = self.value.take().unwrap_or_else(String::new); - println!("Value check: {} is? {}", old_value, new_value); + debug!("Value check: {} is? {}", old_value, new_value); if old_value != new_value { input.set_value(new_value); } @@ -215,17 +229,17 @@ impl VNode { } } - println!("VNode classes"); + debug!("VNode classes"); for class in self.classes.iter() { this.class_list().add(&class); } - println!("VNode attributes"); + debug!("VNode attributes"); for (name, value) in self.attributes.iter() { set_attribute(&this, name, &value); } - println!("VNode listeners"); + debug!("VNode listeners"); // TODO IMPORTANT! IT DUPLICATES ALL LISTENERS! // How to fix? What about to use "global" list of // listeners mapping by dom references. @@ -233,7 +247,7 @@ impl VNode { listener.attach(&this, messages.clone()); } - println!("VNode children"); + debug!("VNode children"); let mut childs = self.childs.drain(..).map(Some).collect::>(); let mut nodes = this.child_nodes().iter().map(Some).collect::>(); let diff = childs.len() as i32 - nodes.len() as i32; @@ -266,18 +280,18 @@ impl VNode { impl Render for VNode { fn render(&mut self, parent: &T, this: Option, messages: Messages) { - println!("Render: {:?}", this); + debug!("Render: {:?}", this); if let Some(this) = this { - println!("Node: {:?}", this.node_name()); + debug!("Node: {:?}", this.node_name()); // Important! HTML Expected! if self.tag.to_owned().to_uppercase() == this.node_name() { let this = this.try_into().unwrap(); self.fill_node(&this, messages.clone()); } else { - println!("REPLACE!"); + debug!("REPLACE!"); let element = document().create_element(self.tag); parent.replace_child(&element, &this); - println!("REPLACE DONE!"); + debug!("REPLACE DONE!"); self.fill_node(&element, messages.clone()); } } else { @@ -322,7 +336,7 @@ macro_rules! impl_action { let handler = self.0.take().unwrap(); let this = element.clone(); let sender = move |event: $type| { - println!("Event handler: {}", stringify!($type)); + debug!("Event handler: {}", stringify!($type)); event.stop_propagation(); let handy_event: $ret = $convert(&this, event); let msg = handler(handy_event); From cf9982c44754222991fc199c730f0bcf47028edf Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sat, 23 Dec 2017 11:49:54 +0300 Subject: [PATCH 05/14] Add VNode abstraction --- Cargo.toml | 3 +- examples/todomvc/Cargo.lock | 6 +- src/html.rs | 247 ++++++++++++++++++++++++++++-------- src/macros.rs | 16 +-- 4 files changed, 210 insertions(+), 62 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 97b0fabafaa..b97e16bc87b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" authors = ["Denis Kolodin "] [dependencies] -stdweb = { git = "https://github.com/koute/stdweb" } +stdweb = { git = "https://github.com/DenisKolodin/stdweb", branch = "pub-textnode" } +#stdweb = { git = "https://github.com/koute/stdweb" } diff --git a/examples/todomvc/Cargo.lock b/examples/todomvc/Cargo.lock index 5562944149a..570427b59f6 100644 --- a/examples/todomvc/Cargo.lock +++ b/examples/todomvc/Cargo.lock @@ -32,7 +32,7 @@ dependencies = [ [[package]] name = "stdweb" version = "0.2.0" -source = "git+https://github.com/koute/stdweb#9ba27f1c317db65049f675834485d3d12405e1f3" +source = "git+https://github.com/DenisKolodin/stdweb?branch=pub-textnode#6c0e2b7b096051865b1bc7975b42258d94518d45" dependencies = [ "serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -49,7 +49,7 @@ dependencies = [ name = "yew" version = "0.1.0" dependencies = [ - "stdweb 0.2.0 (git+https://github.com/koute/stdweb)", + "stdweb 0.2.0 (git+https://github.com/DenisKolodin/stdweb?branch=pub-textnode)", ] [metadata] @@ -58,4 +58,4 @@ dependencies = [ "checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070" "checksum serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "1c57ab4ec5fa85d08aaf8ed9245899d9bbdd66768945b21113b84d5f595cb6a1" "checksum serde_json 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7cf5b0b5b4bd22eeecb7e01ac2e1225c7ef5e4272b79ee28a8392a8c8489c839" -"checksum stdweb 0.2.0 (git+https://github.com/koute/stdweb)" = "" +"checksum stdweb 0.2.0 (git+https://github.com/DenisKolodin/stdweb?branch=pub-textnode)" = "" diff --git a/src/html.rs b/src/html.rs index b918fdc9153..3a1057f9c1b 100644 --- a/src/html.rs +++ b/src/html.rs @@ -5,7 +5,8 @@ use std::collections::HashMap; use stdweb; -use stdweb::web::{INode, IElement, Node, Element, document}; +use stdweb::Reference; +use stdweb::web::{INode, IElement, Node, Element, TextNode, document}; use stdweb::web::event::{IMouseEvent, IKeyboardEvent}; use stdweb::web::html_element::InputElement; use stdweb::unstable::TryInto; @@ -24,6 +25,13 @@ macro_rules! warn { }; } +fn clear_body() { + let body = document().query_selector("body").unwrap(); + while body.has_child_nodes() { + body.remove_child(&body.last_child().unwrap()).unwrap(); + } +} + pub fn program(mut model: M, update: U, view: V) where M: 'static, @@ -32,24 +40,24 @@ where V: Fn(&M) -> Html + 'static, { stdweb::initialize(); + clear_body(); let body = document().query_selector("body").unwrap(); - while body.has_child_nodes() { - body.remove_child(&body.last_child().unwrap()).unwrap(); - } - let app = document().create_element("app"); - body.append_child(&app); // No messages at start let messages = Rc::new(RefCell::new(Vec::new())); + let mut last_frame = VNode::from(view(&model)); + last_frame.apply(&body, None, messages.clone()); + let mut last_frame = Some(last_frame); + let mut callback = move || { debug!("Yew Loop Callback"); let mut borrowed = messages.borrow_mut(); for msg in borrowed.drain(..) { update(&mut model, msg); } - let mut html = view(&model); - debug!("Do render"); - let this = body.first_child(); - html.render(&body, this, messages.clone()); + let mut next_frame = VNode::from(view(&model)); + debug!("Do apply"); + next_frame.apply(&body, last_frame.take(), messages.clone()); + last_frame = Some(next_frame); }; // Initial call for first rendering callback(); @@ -62,7 +70,7 @@ where stdweb::event_loop(); } -pub type Html = VNode; +pub type Html = VTag; pub trait Listener { fn kind(&self) -> &'static str; @@ -80,50 +88,152 @@ type Listeners = Vec>>; type Attributes = HashMap<&'static str, String>; type Classes = Vec<&'static str>; -trait Render { - fn render(&mut self, parent: &T, this: Option, messages: Messages); -} - -pub enum Child { - VNode(VNode), - VText(VText), +/// Bind virtual element to a DOM reference. +pub enum VNode { + VTag { + reference: Option, + vtag: VTag, + }, + VText { + reference: Option, // TODO Replace with TextNode + vtext: VText, + }, } -impl From for Child { +impl From for VNode { fn from(value: T) -> Self { - Child::VText(VText::new(value)) + VNode::VText { + reference: None, + vtext: VText::new(value), + } } } -impl fmt::Debug for Child { +impl fmt::Debug for VNode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - &Child::VNode(ref vnode) => vnode.fmt(f), - &Child::VText(ref vtext) => vtext.fmt(f), + &VNode::VTag { ref vtag, .. } => vtag.fmt(f), + &VNode::VText { ref vtext, .. } => vtext.fmt(f), } } } +impl VNode { + fn remove(self, parent: &T) { + let opt_ref: Option = { + match self { + VNode::VTag { reference, .. } => reference.map(Node::from), + VNode::VText { reference, .. } => reference.map(Node::from), + } + }; + if let Some(node) = opt_ref { + if let Err(_) = parent.remove_child(&node) { + warn!("Node not found to remove: {:?}", node); + } + } + } -impl Render for Child { - fn render(&mut self, parent: &T, this: Option, messages: Messages) { + fn apply(&mut self, parent: &T, last: Option>, messages: Messages) { match *self { - Child::VNode(ref mut vnode) => vnode.render(parent, this, messages), - Child::VText(ref mut vtext) => vtext.render(parent, this, messages), + VNode::VTag { ref mut vtag, ref mut reference } => { + let left = vtag; + let mut right = None; + match last { + Some(VNode::VTag { mut vtag, reference: Some(mut element) }) => { + // Copy reference from right to left (as is) + right = Some(vtag); + *reference = Some(element); + } + Some(VNode::VText { reference: Some(old), .. }) => { + let mut element = document().create_element(left.tag); + parent.replace_child(&element, &old); + left.render(&mut element, None, messages.clone()); + *reference = Some(element); + } + Some(VNode::VTag { reference: None, .. }) | + Some(VNode::VText { reference: None, .. }) | + None => { + let mut element = document().create_element(left.tag); + parent.append_child(&element); + left.render(&mut element, None, messages.clone()); + *reference = Some(element); + } + } + let element_mut = reference.as_mut().expect("vtag must be here"); + // Update parameters + let mut rights = { + if let Some(ref mut right) = right { + right.childs.drain(..).map(Some).collect::>() + } else { + Vec::new() + } + }; + // TODO Consider to use: &mut Messages here; + left.render(element_mut, right, messages.clone()); + let mut lefts = left.childs.iter_mut().map(Some).collect::>(); + // Process children + let diff = lefts.len() as i32 - rights.len() as i32; + if diff > 0 { + for _ in 0..diff { + rights.push(None); + } + } else if diff < 0 { + for _ in 0..-diff { + lefts.push(None); + } + } + for pair in lefts.into_iter().zip(rights) { + match pair { + (Some(left), right) => { + left.apply(element_mut, right, messages.clone()); + } + (None, Some(right)) => { + right.remove(element_mut); + } + (None, None) => { + panic!("redundant iterations during diff"); + } + } + } + //vtag.apply(parent, reference, last, messages); + } + VNode::VText { ref mut vtext, ref mut reference } => { + match last { + Some(VNode::VTag { .. }) => { + } + Some(VNode::VText { .. }) => { + // TODO Replace the node + } + Some(VNode::VTag { reference: None, .. }) | + Some(VNode::VText { reference: None, .. }) | + None => { + let element = document().create_text_node(&vtext.text); + parent.append_child(&element); + *reference = Some(element); + } + } + //vtext.apply(parent, reference, last, messages); + } } } } -impl From for Child { +impl From for VNode { fn from(vtext: VText) -> Self { - Child::VText(vtext) + VNode::VText { + reference: None, + vtext, + } } } -impl From> for Child { - fn from(vnode: VNode) -> Self { - Child::VNode(vnode) +impl From> for VNode { + fn from(vtag: VTag) -> Self { + VNode::VTag { + reference: None, + vtag, + } } } @@ -141,10 +251,9 @@ impl VText { pub fn new(text: T) -> Self { VText { text: text.to_string() } } -} -impl Render for VText { - fn render(&mut self, parent: &T, this: Option, _: Messages) { + fn apply(&mut self, parent: &T, opposite: Option, _: Messages) { + /* debug!("Render text node!"); if let Some(_) = this { // Check node type and replace if wrong @@ -152,28 +261,29 @@ impl Render for VText { let element = document().create_text_node(&self.text); parent.append_child(&element); } + */ } } -pub struct VNode { +pub struct VTag { tag: &'static str, listeners: Listeners, attributes: Attributes, - childs: Vec>, + childs: Vec>, classes: Classes, value: Option, kind: Option, } -impl fmt::Debug for VNode { +impl fmt::Debug for VTag { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "VNode {{ tag: {} }}", self.tag) + write!(f, "VTag {{ tag: {} }}", self.tag) } } -impl VNode { +impl VTag { pub fn new(tag: &'static str) -> Self { - VNode { + VTag { tag: tag, classes: Vec::new(), attributes: HashMap::new(), @@ -188,7 +298,7 @@ impl VNode { self.tag } - pub fn add_child(&mut self, child: Child) { + pub fn add_child(&mut self, child: VNode) { self.childs.push(child); } @@ -212,8 +322,9 @@ impl VNode { self.listeners.push(listener); } + /* fn fill_node(&mut self, this: &Element, messages: Messages) { - debug!("Fill VNode"); + debug!("Fill VTag"); let input: Result = this.clone().try_into(); if let Ok(input) = input { let old_value = input.value().into_string().unwrap(); @@ -229,17 +340,17 @@ impl VNode { } } - debug!("VNode classes"); + debug!("VTag classes"); for class in self.classes.iter() { this.class_list().add(&class); } - debug!("VNode attributes"); + debug!("VTag attributes"); for (name, value) in self.attributes.iter() { set_attribute(&this, name, &value); } - debug!("VNode listeners"); + debug!("VTag listeners"); // TODO IMPORTANT! IT DUPLICATES ALL LISTENERS! // How to fix? What about to use "global" list of // listeners mapping by dom references. @@ -247,7 +358,7 @@ impl VNode { listener.attach(&this, messages.clone()); } - debug!("VNode children"); + debug!("VTag children"); let mut childs = self.childs.drain(..).map(Some).collect::>(); let mut nodes = this.child_nodes().iter().map(Some).collect::>(); let diff = childs.len() as i32 - nodes.len() as i32; @@ -264,7 +375,7 @@ impl VNode { for pair in childs.into_iter().zip(nodes) { match pair { (Some(mut child), node) => { - child.render(this, node, messages.clone()); + child.apply(this, node, messages.clone()); } (None, Some(node)) => { this.remove_child(&node).unwrap(); @@ -276,10 +387,45 @@ impl VNode { } } } + */ } -impl Render for VNode { - fn render(&mut self, parent: &T, this: Option, messages: Messages) { +impl VTag { + fn render(&mut self, subject: &Element, opposite: Option>, messages: Messages) { + /* + let children = { + if let Some(vnode) = this { + match vnode { + VNode::VTag { vtag, .. } => { + if self.tag != vtag.tag { + let element = document().create_element(self.tag); + let node = reference.take().unwrap().into(); + parent.replace_child(&element, &node); + *reference = Some(element.into()); + Vec::new() + } else { + // TODO Check the difference! + vtag.childs + } + } + VNode::VText { vtext, .. } => { + let element = document().create_element(self.tag); + let node = reference.take().unwrap().into(); + parent.replace_child(&element, &node); + *reference = Some(element.into()); + Vec::new() + } + } + } else { + // Creates an element, put it as child to a parent and save the reference to DOM + let element = document().create_element(self.tag); + parent.append_child(&element); + *reference = Some(element.into()); + Vec::new() + } + }; + */ + /* debug!("Render: {:?}", this); if let Some(this) = this { debug!("Node: {:?}", this.node_name()); @@ -299,6 +445,7 @@ impl Render for VNode { parent.append_child(&element); self.fill_node(&element, messages.clone()); } + */ } } diff --git a/src/macros.rs b/src/macros.rs index bb0a9400e25..8c78dfa90c5 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,10 +1,10 @@ -use html::{VNode, Child, Listener}; +use html::{VTag, VNode, Listener}; #[macro_export] macro_rules! html_impl { // Start of openging tag ($stack:ident (< $starttag:ident $($tail:tt)*)) => { - let node = $crate::html::VNode::new(stringify!($starttag)); + let node = $crate::html::VTag::new(stringify!($starttag)); $stack.push(node); html_impl! { $stack ($($tail)*) } }; @@ -52,14 +52,14 @@ macro_rules! html_impl { // PATTERN: { for expression } ($stack:ident ({ for $eval:expr } $($tail:tt)*)) => { let nodes = $eval; - for node in nodes.map($crate::html::Child::from) { + for node in nodes.map($crate::html::VNode::from) { $crate::macros::add_child(&mut $stack, node); } html_impl! { $stack ($($tail)*) } }; // PATTERN: { expression } ($stack:ident ({ $eval:expr } $($tail:tt)*)) => { - let node = $crate::html::Child::from($eval); + let node = $crate::html::VNode::from($eval); $crate::macros::add_child(&mut $stack, node); html_impl! { $stack ($($tail)*) } }; @@ -93,10 +93,10 @@ macro_rules! html { }; } -type Stack = Vec>; +type Stack = Vec>; #[doc(hidden)] -pub fn unpack(mut stack: Stack) -> VNode { +pub fn unpack(mut stack: Stack) -> VTag { if stack.len() != 1 { panic!("exactly one element have to be in html!"); } @@ -149,7 +149,7 @@ pub fn attach_listener(stack: &mut Stack, listener: Box> } #[doc(hidden)] -pub fn add_child(stack: &mut Stack, child: Child) { +pub fn add_child(stack: &mut Stack, child: VNode) { if let Some(parent) = stack.last_mut() { parent.add_child(child); } else { @@ -167,7 +167,7 @@ pub fn child_to_parent(stack: &mut Stack, endtag: Option<&'static str> } } if !stack.is_empty() { - stack.last_mut().unwrap().add_child(Child::VNode(node)); + stack.last_mut().unwrap().add_child(VNode::from(node)); } else { // Keep the last node in the stack stack.push(node); From 92ea1dfd1af3edd06718174422474a0cbfa0a664 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sat, 23 Dec 2017 12:12:30 +0300 Subject: [PATCH 06/14] Add or remove classes to VTag on render --- src/html.rs | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/html.rs b/src/html.rs index 3a1057f9c1b..5e152b10079 100644 --- a/src/html.rs +++ b/src/html.rs @@ -1,7 +1,7 @@ use std::fmt; use std::rc::Rc; use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use stdweb; @@ -86,7 +86,7 @@ impl fmt::Debug for Listener { type Messages = Rc>>; type Listeners = Vec>>; type Attributes = HashMap<&'static str, String>; -type Classes = Vec<&'static str>; +type Classes = HashSet<&'static str>; /// Bind virtual element to a DOM reference. pub enum VNode { @@ -281,11 +281,16 @@ impl fmt::Debug for VTag { } } +enum Mutator { + Add(ID, T), + Remove(ID), +} + impl VTag { pub fn new(tag: &'static str) -> Self { VTag { tag: tag, - classes: Vec::new(), + classes: Classes::new(), attributes: HashMap::new(), listeners: Vec::new(), childs: Vec::new(), @@ -303,7 +308,7 @@ impl VTag { } pub fn add_classes(&mut self, class: &'static str) { - self.classes.push(class); + self.classes.insert(class); } pub fn set_value(&mut self, value: &T) { @@ -322,6 +327,18 @@ impl VTag { self.listeners.push(listener); } + fn soakup_classes(&mut self, ancestor: &mut Option) -> Vec> { + if let &mut Some(ref ancestor) = ancestor { + let changes = Vec::new(); + let add = self.classes.difference(&ancestor.classes); + let remove = ancestor.classes.difference(&self.classes); + changes + } else { + // Add everything + self.classes.iter().map(|class| Mutator::Add(*class, ())).collect() + } + } + /* fn fill_node(&mut self, this: &Element, messages: Messages) { debug!("Fill VTag"); @@ -391,7 +408,19 @@ impl VTag { } impl VTag { - fn render(&mut self, subject: &Element, opposite: Option>, messages: Messages) { + fn render(&mut self, subject: &Element, mut opposite: Option>, messages: Messages) { + let changes = self.soakup_classes(&mut opposite); + for change in changes { + let list = subject.class_list(); + match change { + Mutator::Add(class, _) => { + list.add(&class); + } + Mutator::Remove(class) => { + list.remove(&class); + } + } + } /* let children = { if let Some(vnode) = this { From a27a2a3e9f6cf8737a33543420299d915e7b79c4 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sat, 23 Dec 2017 12:56:13 +0300 Subject: [PATCH 07/14] Render and soak up attributes and input kind (type) --- src/html.rs | 108 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 6 deletions(-) diff --git a/src/html.rs b/src/html.rs index 5e152b10079..345a041e05a 100644 --- a/src/html.rs +++ b/src/html.rs @@ -283,6 +283,7 @@ impl fmt::Debug for VTag { enum Mutator { Add(ID, T), + Replace(ID, T), Remove(ID), } @@ -328,14 +329,75 @@ impl VTag { } fn soakup_classes(&mut self, ancestor: &mut Option) -> Vec> { + let mut changes = Vec::new(); if let &mut Some(ref ancestor) = ancestor { - let changes = Vec::new(); - let add = self.classes.difference(&ancestor.classes); - let remove = ancestor.classes.difference(&self.classes); - changes + let to_add = self.classes + .difference(&ancestor.classes) + .map(|class| Mutator::Add(*class, ())); + changes.extend(to_add); + let to_remove = ancestor.classes + .difference(&self.classes) + .map(|class| Mutator::Remove(*class)); + changes.extend(to_remove); } else { // Add everything - self.classes.iter().map(|class| Mutator::Add(*class, ())).collect() + let to_add = self.classes.iter().map(|class| Mutator::Add(*class, ())); + changes.extend(to_add); + } + changes + } + + fn soakup_attributes(&mut self, ancestor: &mut Option) -> Vec> { + let mut changes = Vec::new(); + if let &mut Some(ref mut ancestor) = ancestor { + let left_keys = self.attributes.keys().collect::>(); + let right_keys = ancestor.attributes.keys().collect::>(); + let to_add = left_keys + .difference(&right_keys) + .map(|key| { + let value = self.attributes.get(*key).unwrap(); + Mutator::Add(key.to_string(), value.to_string()) + }); + changes.extend(to_add); + for key in left_keys.intersection(&right_keys) { + let left_value = self.attributes.get(*key).unwrap(); + let right_value = ancestor.attributes.get(*key).unwrap(); + if left_value != right_value { + let mutator = Mutator::Replace(key.to_string(), left_value.to_string()); + changes.push(mutator); + } + } + let to_remove = right_keys + .difference(&left_keys) + .map(|key| Mutator::Remove(key.to_string())); + changes.extend(to_remove); + } else { + for (key, value) in self.attributes.iter() { + let mutator = Mutator::Add(key.to_string(), value.to_string()); + changes.push(mutator); + } + } + changes + } + + fn soakup_kind(&mut self, ancestor: &mut Option) -> Option> { + match (&self.kind, ancestor.as_mut().and_then(|anc| anc.kind.take())) { + (&Some(ref left), Some(ref right)) => { + if left != right { + Some(Mutator::Replace(left.to_string(), ())) + } else { + None + } + } + (&Some(ref left), None) => { + Some(Mutator::Add(left.to_string(), ())) + } + (&None, Some(right)) => { + Some(Mutator::Remove(right)) + } + (&None, None) => { + None + } } } @@ -413,7 +475,7 @@ impl VTag { for change in changes { let list = subject.class_list(); match change { - Mutator::Add(class, _) => { + Mutator::Add(class, _) | Mutator::Replace(class, _) => { list.add(&class); } Mutator::Remove(class) => { @@ -421,6 +483,36 @@ impl VTag { } } } + + let changes = self.soakup_attributes(&mut opposite); + for change in changes { + let list = subject.class_list(); + match change { + Mutator::Add(key, value) | Mutator::Replace(key, value) => { + set_attribute(&subject, &key, &value); + } + Mutator::Remove(key) => { + remove_attribute(&subject, &key); + } + } + } + + if let Some(change) = self.soakup_kind(&mut opposite) { + let input: Result = subject.clone().try_into(); + if let Ok(input) = input { + match change { + Mutator::Add(kind, _) | Mutator::Replace(kind, _) => { + input.set_kind(&kind); + } + Mutator::Remove(kind) => { + input.set_kind(""); + } + } + } else { + panic!("tried to set `type` kind for non input element"); + } + } + /* let children = { if let Some(vnode) = this { @@ -594,3 +686,7 @@ fn set_attribute(element: &Element, name: &str, value: &str) { js!( @{element}.setAttribute( @{name}, @{value} ); ); } +fn remove_attribute(element: &Element, name: &str) { + js!( @{element}.removeAttribute( @{name} ); ); +} + From c01aee9dfbbadd7209790c895f8c35616e1b5b5d Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sat, 23 Dec 2017 13:35:19 +0300 Subject: [PATCH 08/14] Set value in VTag --- src/html.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/html.rs b/src/html.rs index 345a041e05a..2909c4f53df 100644 --- a/src/html.rs +++ b/src/html.rs @@ -401,6 +401,27 @@ impl VTag { } } + fn soakup_value(&mut self, ancestor: &mut Option) -> Option> { + match (&self.value, ancestor.as_mut().and_then(|anc| anc.value.take())) { + (&Some(ref left), Some(ref right)) => { + if left != right { + Some(Mutator::Replace(left.to_string(), ())) + } else { + None + } + } + (&Some(ref left), None) => { + Some(Mutator::Add(left.to_string(), ())) + } + (&None, Some(right)) => { + Some(Mutator::Remove(right)) + } + (&None, None) => { + None + } + } + } + /* fn fill_node(&mut self, this: &Element, messages: Messages) { debug!("Fill VTag"); @@ -471,6 +492,8 @@ impl VTag { impl VTag { fn render(&mut self, subject: &Element, mut opposite: Option>, messages: Messages) { + // TODO Replace self if tagName differs + let changes = self.soakup_classes(&mut opposite); for change in changes { let list = subject.class_list(); @@ -513,6 +536,22 @@ impl VTag { } } + if let Some(change) = self.soakup_value(&mut opposite) { + let input: Result = subject.clone().try_into(); + if let Ok(input) = input { + match change { + Mutator::Add(kind, _) | Mutator::Replace(kind, _) => { + input.set_value(&kind); + } + Mutator::Remove(kind) => { + input.set_value(""); + } + } + } else { + panic!("tried to set `value` kind for non input element"); + } + } + /* let children = { if let Some(vnode) = this { From 269d4a8f171f9d6a2647241d91728ed2ed452b99 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sat, 23 Dec 2017 13:39:42 +0300 Subject: [PATCH 09/14] Attach listeners to DOM subject (naive) --- src/html.rs | 111 ++-------------------------------------------------- 1 file changed, 3 insertions(+), 108 deletions(-) diff --git a/src/html.rs b/src/html.rs index 2909c4f53df..50783333bb2 100644 --- a/src/html.rs +++ b/src/html.rs @@ -424,32 +424,6 @@ impl VTag { /* fn fill_node(&mut self, this: &Element, messages: Messages) { - debug!("Fill VTag"); - let input: Result = this.clone().try_into(); - if let Ok(input) = input { - let old_value = input.value().into_string().unwrap(); - let new_value = self.value.take().unwrap_or_else(String::new); - debug!("Value check: {} is? {}", old_value, new_value); - if old_value != new_value { - input.set_value(new_value); - } - if let Some(ref kind) = self.kind { - input.set_kind(kind); - } else { - input.set_kind(""); - } - } - - debug!("VTag classes"); - for class in self.classes.iter() { - this.class_list().add(&class); - } - - debug!("VTag attributes"); - for (name, value) in self.attributes.iter() { - set_attribute(&this, name, &value); - } - debug!("VTag listeners"); // TODO IMPORTANT! IT DUPLICATES ALL LISTENERS! // How to fix? What about to use "global" list of @@ -457,35 +431,6 @@ impl VTag { for mut listener in self.listeners.drain(..) { listener.attach(&this, messages.clone()); } - - debug!("VTag children"); - let mut childs = self.childs.drain(..).map(Some).collect::>(); - let mut nodes = this.child_nodes().iter().map(Some).collect::>(); - let diff = childs.len() as i32 - nodes.len() as i32; - if diff > 0 { - for _ in 0..diff { - nodes.push(None); - } - } else if diff < 0 { - for _ in 0..-diff { - childs.push(None); - } - } - - for pair in childs.into_iter().zip(nodes) { - match pair { - (Some(mut child), node) => { - child.apply(this, node, messages.clone()); - } - (None, Some(node)) => { - this.remove_child(&node).unwrap(); - // Remove redundant node - } - (None, None) => { - panic!("both nodes are not existent during comparsion"); - } - } - } } */ } @@ -552,60 +497,10 @@ impl VTag { } } - /* - let children = { - if let Some(vnode) = this { - match vnode { - VNode::VTag { vtag, .. } => { - if self.tag != vtag.tag { - let element = document().create_element(self.tag); - let node = reference.take().unwrap().into(); - parent.replace_child(&element, &node); - *reference = Some(element.into()); - Vec::new() - } else { - // TODO Check the difference! - vtag.childs - } - } - VNode::VText { vtext, .. } => { - let element = document().create_element(self.tag); - let node = reference.take().unwrap().into(); - parent.replace_child(&element, &node); - *reference = Some(element.into()); - Vec::new() - } - } - } else { - // Creates an element, put it as child to a parent and save the reference to DOM - let element = document().create_element(self.tag); - parent.append_child(&element); - *reference = Some(element.into()); - Vec::new() - } - }; - */ - /* - debug!("Render: {:?}", this); - if let Some(this) = this { - debug!("Node: {:?}", this.node_name()); - // Important! HTML Expected! - if self.tag.to_owned().to_uppercase() == this.node_name() { - let this = this.try_into().unwrap(); - self.fill_node(&this, messages.clone()); - } else { - debug!("REPLACE!"); - let element = document().create_element(self.tag); - parent.replace_child(&element, &this); - debug!("REPLACE DONE!"); - self.fill_node(&element, messages.clone()); - } - } else { - let element = document().create_element(self.tag); - parent.append_child(&element); - self.fill_node(&element, messages.clone()); + // TODO Actually, it duplicates listeners on every call + for mut listener in self.listeners.drain(..) { + listener.attach(&subject, messages.clone()); } - */ } } From 35692f40d90c39f6db4706b9a369ba80dce28aa2 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sat, 23 Dec 2017 14:02:51 +0300 Subject: [PATCH 10/14] Drop and reattach listeners to elements It's used to prevent handlers duplications till we don't have a good approach to compare closures and chage listeners better. --- src/html.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/html.rs b/src/html.rs index 50783333bb2..087743642ee 100644 --- a/src/html.rs +++ b/src/html.rs @@ -6,7 +6,7 @@ use std::collections::{HashMap, HashSet}; use stdweb; use stdweb::Reference; -use stdweb::web::{INode, IElement, Node, Element, TextNode, document}; +use stdweb::web::{INode, IElement, Node, Element, TextNode, EventListenerHandle, document}; use stdweb::web::event::{IMouseEvent, IKeyboardEvent}; use stdweb::web::html_element::InputElement; use stdweb::unstable::TryInto; @@ -74,7 +74,7 @@ pub type Html = VTag; pub trait Listener { fn kind(&self) -> &'static str; - fn attach(&mut self, element: &Element, messages: Messages); + fn attach(&mut self, element: &Element, messages: Messages) -> EventListenerHandle; } impl fmt::Debug for Listener { @@ -268,6 +268,7 @@ impl VText { pub struct VTag { tag: &'static str, listeners: Listeners, + captured: Vec, attributes: Attributes, childs: Vec>, classes: Classes, @@ -294,6 +295,7 @@ impl VTag { classes: Classes::new(), attributes: HashMap::new(), listeners: Vec::new(), + captured: Vec::new(), childs: Vec::new(), value: None, kind: None, @@ -421,18 +423,6 @@ impl VTag { } } } - - /* - fn fill_node(&mut self, this: &Element, messages: Messages) { - debug!("VTag listeners"); - // TODO IMPORTANT! IT DUPLICATES ALL LISTENERS! - // How to fix? What about to use "global" list of - // listeners mapping by dom references. - for mut listener in self.listeners.drain(..) { - listener.attach(&this, messages.clone()); - } - } - */ } impl VTag { @@ -497,9 +487,19 @@ impl VTag { } } - // TODO Actually, it duplicates listeners on every call + // Every render it removes all listeners and attach it back later + // TODO Compare references of handler to do listeners update better + if let Some(mut opposite) = opposite { + for mut handle in opposite.captured.drain(..) { + debug!("Removing handler..."); + handle.remove(); + } + } + for mut listener in self.listeners.drain(..) { - listener.attach(&subject, messages.clone()); + debug!("Add listener..."); + let handle = listener.attach(&subject, messages.clone()); + self.captured.push(handle); } } } @@ -534,7 +534,7 @@ macro_rules! impl_action { stringify!($action) } - fn attach(&mut self, element: &Element, messages: Messages) { + fn attach(&mut self, element: &Element, messages: Messages) -> EventListenerHandle { let handler = self.0.take().unwrap(); let this = element.clone(); let sender = move |event: $type| { @@ -551,7 +551,7 @@ macro_rules! impl_action { setTimeout(yew_loop); } }; - element.add_event_listener(sender); + element.add_event_listener(sender) } } } From f5e9fc38f5faa83676df10956ffd74020abf920b Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sat, 23 Dec 2017 14:06:27 +0300 Subject: [PATCH 11/14] Rename Mutator to Patch --- examples/todomvc/src/main.rs | 1 + src/html.rs | 52 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/examples/todomvc/src/main.rs b/examples/todomvc/src/main.rs index 729934c1e36..13292db8e9b 100644 --- a/examples/todomvc/src/main.rs +++ b/examples/todomvc/src/main.rs @@ -59,6 +59,7 @@ fn update(model: &mut Model, msg: Msg) { completed: false, }; model.entries.push(entry); + model.value = "".to_string(); } Msg::Update(val) => { println!("Input: {}", val); diff --git a/src/html.rs b/src/html.rs index 087743642ee..d2766364c97 100644 --- a/src/html.rs +++ b/src/html.rs @@ -282,7 +282,7 @@ impl fmt::Debug for VTag { } } -enum Mutator { +enum Patch { Add(ID, T), Replace(ID, T), Remove(ID), @@ -330,26 +330,26 @@ impl VTag { self.listeners.push(listener); } - fn soakup_classes(&mut self, ancestor: &mut Option) -> Vec> { + fn soakup_classes(&mut self, ancestor: &mut Option) -> Vec> { let mut changes = Vec::new(); if let &mut Some(ref ancestor) = ancestor { let to_add = self.classes .difference(&ancestor.classes) - .map(|class| Mutator::Add(*class, ())); + .map(|class| Patch::Add(*class, ())); changes.extend(to_add); let to_remove = ancestor.classes .difference(&self.classes) - .map(|class| Mutator::Remove(*class)); + .map(|class| Patch::Remove(*class)); changes.extend(to_remove); } else { // Add everything - let to_add = self.classes.iter().map(|class| Mutator::Add(*class, ())); + let to_add = self.classes.iter().map(|class| Patch::Add(*class, ())); changes.extend(to_add); } changes } - fn soakup_attributes(&mut self, ancestor: &mut Option) -> Vec> { + fn soakup_attributes(&mut self, ancestor: &mut Option) -> Vec> { let mut changes = Vec::new(); if let &mut Some(ref mut ancestor) = ancestor { let left_keys = self.attributes.keys().collect::>(); @@ -358,44 +358,44 @@ impl VTag { .difference(&right_keys) .map(|key| { let value = self.attributes.get(*key).unwrap(); - Mutator::Add(key.to_string(), value.to_string()) + Patch::Add(key.to_string(), value.to_string()) }); changes.extend(to_add); for key in left_keys.intersection(&right_keys) { let left_value = self.attributes.get(*key).unwrap(); let right_value = ancestor.attributes.get(*key).unwrap(); if left_value != right_value { - let mutator = Mutator::Replace(key.to_string(), left_value.to_string()); + let mutator = Patch::Replace(key.to_string(), left_value.to_string()); changes.push(mutator); } } let to_remove = right_keys .difference(&left_keys) - .map(|key| Mutator::Remove(key.to_string())); + .map(|key| Patch::Remove(key.to_string())); changes.extend(to_remove); } else { for (key, value) in self.attributes.iter() { - let mutator = Mutator::Add(key.to_string(), value.to_string()); + let mutator = Patch::Add(key.to_string(), value.to_string()); changes.push(mutator); } } changes } - fn soakup_kind(&mut self, ancestor: &mut Option) -> Option> { + fn soakup_kind(&mut self, ancestor: &mut Option) -> Option> { match (&self.kind, ancestor.as_mut().and_then(|anc| anc.kind.take())) { (&Some(ref left), Some(ref right)) => { if left != right { - Some(Mutator::Replace(left.to_string(), ())) + Some(Patch::Replace(left.to_string(), ())) } else { None } } (&Some(ref left), None) => { - Some(Mutator::Add(left.to_string(), ())) + Some(Patch::Add(left.to_string(), ())) } (&None, Some(right)) => { - Some(Mutator::Remove(right)) + Some(Patch::Remove(right)) } (&None, None) => { None @@ -403,20 +403,20 @@ impl VTag { } } - fn soakup_value(&mut self, ancestor: &mut Option) -> Option> { + fn soakup_value(&mut self, ancestor: &mut Option) -> Option> { match (&self.value, ancestor.as_mut().and_then(|anc| anc.value.take())) { (&Some(ref left), Some(ref right)) => { if left != right { - Some(Mutator::Replace(left.to_string(), ())) + Some(Patch::Replace(left.to_string(), ())) } else { None } } (&Some(ref left), None) => { - Some(Mutator::Add(left.to_string(), ())) + Some(Patch::Add(left.to_string(), ())) } (&None, Some(right)) => { - Some(Mutator::Remove(right)) + Some(Patch::Remove(right)) } (&None, None) => { None @@ -433,10 +433,10 @@ impl VTag { for change in changes { let list = subject.class_list(); match change { - Mutator::Add(class, _) | Mutator::Replace(class, _) => { + Patch::Add(class, _) | Patch::Replace(class, _) => { list.add(&class); } - Mutator::Remove(class) => { + Patch::Remove(class) => { list.remove(&class); } } @@ -446,10 +446,10 @@ impl VTag { for change in changes { let list = subject.class_list(); match change { - Mutator::Add(key, value) | Mutator::Replace(key, value) => { + Patch::Add(key, value) | Patch::Replace(key, value) => { set_attribute(&subject, &key, &value); } - Mutator::Remove(key) => { + Patch::Remove(key) => { remove_attribute(&subject, &key); } } @@ -459,10 +459,10 @@ impl VTag { let input: Result = subject.clone().try_into(); if let Ok(input) = input { match change { - Mutator::Add(kind, _) | Mutator::Replace(kind, _) => { + Patch::Add(kind, _) | Patch::Replace(kind, _) => { input.set_kind(&kind); } - Mutator::Remove(kind) => { + Patch::Remove(kind) => { input.set_kind(""); } } @@ -475,10 +475,10 @@ impl VTag { let input: Result = subject.clone().try_into(); if let Ok(input) = input { match change { - Mutator::Add(kind, _) | Mutator::Replace(kind, _) => { + Patch::Add(kind, _) | Patch::Replace(kind, _) => { input.set_value(&kind); } - Mutator::Remove(kind) => { + Patch::Remove(kind) => { input.set_value(""); } } From 4315bdfe2af38ce923b33f2f9edb5d914a71c4de Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sat, 23 Dec 2017 14:36:12 +0300 Subject: [PATCH 12/14] Implement VText rendering --- src/html.rs | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/html.rs b/src/html.rs index d2766364c97..63ddd1c0c64 100644 --- a/src/html.rs +++ b/src/html.rs @@ -5,7 +5,6 @@ use std::collections::{HashMap, HashSet}; use stdweb; -use stdweb::Reference; use stdweb::web::{INode, IElement, Node, Element, TextNode, EventListenerHandle, document}; use stdweb::web::event::{IMouseEvent, IKeyboardEvent}; use stdweb::web::html_element::InputElement; @@ -145,10 +144,9 @@ impl VNode { right = Some(vtag); *reference = Some(element); } - Some(VNode::VText { reference: Some(old), .. }) => { + Some(VNode::VText { reference: Some(wrong), .. }) => { let mut element = document().create_element(left.tag); - parent.replace_child(&element, &old); - left.render(&mut element, None, messages.clone()); + parent.replace_child(&element, &wrong); *reference = Some(element); } Some(VNode::VTag { reference: None, .. }) | @@ -156,7 +154,6 @@ impl VNode { None => { let mut element = document().create_element(left.tag); parent.append_child(&element); - left.render(&mut element, None, messages.clone()); *reference = Some(element); } } @@ -199,21 +196,28 @@ impl VNode { //vtag.apply(parent, reference, last, messages); } VNode::VText { ref mut vtext, ref mut reference } => { + let left = vtext; + let mut right = None; match last { - Some(VNode::VTag { .. }) => { + Some(VNode::VText { mut vtext, reference: Some(mut element) }) => { + right = Some(vtext); + *reference = Some(element); } - Some(VNode::VText { .. }) => { - // TODO Replace the node + Some(VNode::VTag { reference: Some(wrong), .. }) => { + let mut element = document().create_text_node(&left.text); + parent.replace_child(&element, &wrong); + *reference = Some(element); } Some(VNode::VTag { reference: None, .. }) | Some(VNode::VText { reference: None, .. }) | None => { - let element = document().create_text_node(&vtext.text); + let element = document().create_text_node(&left.text); parent.append_child(&element); *reference = Some(element); } } - //vtext.apply(parent, reference, last, messages); + let element_mut = reference.as_mut().expect("vtext must be here"); + left.render(element_mut, right); } } } @@ -252,16 +256,14 @@ impl VText { VText { text: text.to_string() } } - fn apply(&mut self, parent: &T, opposite: Option, _: Messages) { - /* - debug!("Render text node!"); - if let Some(_) = this { - // Check node type and replace if wrong + fn render(&mut self, subject: &TextNode, mut opposite: Option) { + if let Some(opposite) = opposite { + if self.text != opposite.text { + subject.set_node_value(Some(&self.text)); + } } else { - let element = document().create_text_node(&self.text); - parent.append_child(&element); + subject.set_node_value(Some(&self.text)); } - */ } } @@ -426,7 +428,7 @@ impl VTag { } impl VTag { - fn render(&mut self, subject: &Element, mut opposite: Option>, messages: Messages) { + fn render(&mut self, subject: &Element, mut opposite: Option, messages: Messages) { // TODO Replace self if tagName differs let changes = self.soakup_classes(&mut opposite); From 62eca67496237848e6c6f00fc5b2eaa389392be5 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sat, 23 Dec 2017 14:38:06 +0300 Subject: [PATCH 13/14] Tidy up the code --- src/html.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/html.rs b/src/html.rs index 63ddd1c0c64..a000ed61893 100644 --- a/src/html.rs +++ b/src/html.rs @@ -256,7 +256,7 @@ impl VText { VText { text: text.to_string() } } - fn render(&mut self, subject: &TextNode, mut opposite: Option) { + fn render(&mut self, subject: &TextNode, opposite: Option) { if let Some(opposite) = opposite { if self.text != opposite.text { subject.set_node_value(Some(&self.text)); @@ -446,7 +446,6 @@ impl VTag { let changes = self.soakup_attributes(&mut opposite); for change in changes { - let list = subject.class_list(); match change { Patch::Add(key, value) | Patch::Replace(key, value) => { set_attribute(&subject, &key, &value); @@ -464,7 +463,7 @@ impl VTag { Patch::Add(kind, _) | Patch::Replace(kind, _) => { input.set_kind(&kind); } - Patch::Remove(kind) => { + Patch::Remove(_) => { input.set_kind(""); } } @@ -480,7 +479,7 @@ impl VTag { Patch::Add(kind, _) | Patch::Replace(kind, _) => { input.set_value(&kind); } - Patch::Remove(kind) => { + Patch::Remove(_) => { input.set_value(""); } } From 778f20124aafc06838183f8a2658a40aa9a5d9c7 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Sat, 23 Dec 2017 14:39:41 +0300 Subject: [PATCH 14/14] Format the code --- examples/todomvc/src/main.rs | 3 +- src/html.rs | 102 ++++++++++++++++++----------------- 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/examples/todomvc/src/main.rs b/examples/todomvc/src/main.rs index 13292db8e9b..7e152cbd063 100644 --- a/examples/todomvc/src/main.rs +++ b/examples/todomvc/src/main.rs @@ -71,8 +71,7 @@ fn update(model: &mut Model, msg: Msg) { Msg::SetFilter(filter) => { model.filter = filter; } - Msg::Nope => { - } + Msg::Nope => {} } } diff --git a/src/html.rs b/src/html.rs index a000ed61893..73508c7fb82 100644 --- a/src/html.rs +++ b/src/html.rs @@ -135,11 +135,17 @@ impl VNode { fn apply(&mut self, parent: &T, last: Option>, messages: Messages) { match *self { - VNode::VTag { ref mut vtag, ref mut reference } => { + VNode::VTag { + ref mut vtag, + ref mut reference, + } => { let left = vtag; let mut right = None; match last { - Some(VNode::VTag { mut vtag, reference: Some(mut element) }) => { + Some(VNode::VTag { + mut vtag, + reference: Some(mut element), + }) => { // Copy reference from right to left (as is) right = Some(vtag); *reference = Some(element); @@ -195,11 +201,17 @@ impl VNode { } //vtag.apply(parent, reference, last, messages); } - VNode::VText { ref mut vtext, ref mut reference } => { + VNode::VText { + ref mut vtext, + ref mut reference, + } => { let left = vtext; let mut right = None; match last { - Some(VNode::VText { mut vtext, reference: Some(mut element) }) => { + Some(VNode::VText { + mut vtext, + reference: Some(mut element), + }) => { right = Some(vtext); *reference = Some(element); } @@ -335,13 +347,13 @@ impl VTag { fn soakup_classes(&mut self, ancestor: &mut Option) -> Vec> { let mut changes = Vec::new(); if let &mut Some(ref ancestor) = ancestor { - let to_add = self.classes - .difference(&ancestor.classes) - .map(|class| Patch::Add(*class, ())); + let to_add = self.classes.difference(&ancestor.classes).map(|class| { + Patch::Add(*class, ()) + }); changes.extend(to_add); - let to_remove = ancestor.classes - .difference(&self.classes) - .map(|class| Patch::Remove(*class)); + let to_remove = ancestor.classes.difference(&self.classes).map(|class| { + Patch::Remove(*class) + }); changes.extend(to_remove); } else { // Add everything @@ -356,12 +368,10 @@ impl VTag { if let &mut Some(ref mut ancestor) = ancestor { let left_keys = self.attributes.keys().collect::>(); let right_keys = ancestor.attributes.keys().collect::>(); - let to_add = left_keys - .difference(&right_keys) - .map(|key| { - let value = self.attributes.get(*key).unwrap(); - Patch::Add(key.to_string(), value.to_string()) - }); + let to_add = left_keys.difference(&right_keys).map(|key| { + let value = self.attributes.get(*key).unwrap(); + Patch::Add(key.to_string(), value.to_string()) + }); changes.extend(to_add); for key in left_keys.intersection(&right_keys) { let left_value = self.attributes.get(*key).unwrap(); @@ -371,9 +381,9 @@ impl VTag { changes.push(mutator); } } - let to_remove = right_keys - .difference(&left_keys) - .map(|key| Patch::Remove(key.to_string())); + let to_remove = right_keys.difference(&left_keys).map(|key| { + Patch::Remove(key.to_string()) + }); changes.extend(to_remove); } else { for (key, value) in self.attributes.iter() { @@ -385,7 +395,10 @@ impl VTag { } fn soakup_kind(&mut self, ancestor: &mut Option) -> Option> { - match (&self.kind, ancestor.as_mut().and_then(|anc| anc.kind.take())) { + match ( + &self.kind, + ancestor.as_mut().and_then(|anc| anc.kind.take()), + ) { (&Some(ref left), Some(ref right)) => { if left != right { Some(Patch::Replace(left.to_string(), ())) @@ -393,20 +406,17 @@ impl VTag { None } } - (&Some(ref left), None) => { - Some(Patch::Add(left.to_string(), ())) - } - (&None, Some(right)) => { - Some(Patch::Remove(right)) - } - (&None, None) => { - None - } + (&Some(ref left), None) => Some(Patch::Add(left.to_string(), ())), + (&None, Some(right)) => Some(Patch::Remove(right)), + (&None, None) => None, } } fn soakup_value(&mut self, ancestor: &mut Option) -> Option> { - match (&self.value, ancestor.as_mut().and_then(|anc| anc.value.take())) { + match ( + &self.value, + ancestor.as_mut().and_then(|anc| anc.value.take()), + ) { (&Some(ref left), Some(ref right)) => { if left != right { Some(Patch::Replace(left.to_string(), ())) @@ -414,15 +424,9 @@ impl VTag { None } } - (&Some(ref left), None) => { - Some(Patch::Add(left.to_string(), ())) - } - (&None, Some(right)) => { - Some(Patch::Remove(right)) - } - (&None, None) => { - None - } + (&Some(ref left), None) => Some(Patch::Add(left.to_string(), ())), + (&None, Some(right)) => Some(Patch::Remove(right)), + (&None, None) => None, } } } @@ -435,7 +439,8 @@ impl VTag { for change in changes { let list = subject.class_list(); match change { - Patch::Add(class, _) | Patch::Replace(class, _) => { + Patch::Add(class, _) | + Patch::Replace(class, _) => { list.add(&class); } Patch::Remove(class) => { @@ -447,7 +452,8 @@ impl VTag { let changes = self.soakup_attributes(&mut opposite); for change in changes { match change { - Patch::Add(key, value) | Patch::Replace(key, value) => { + Patch::Add(key, value) | + Patch::Replace(key, value) => { set_attribute(&subject, &key, &value); } Patch::Remove(key) => { @@ -460,7 +466,8 @@ impl VTag { let input: Result = subject.clone().try_into(); if let Ok(input) = input { match change { - Patch::Add(kind, _) | Patch::Replace(kind, _) => { + Patch::Add(kind, _) | + Patch::Replace(kind, _) => { input.set_kind(&kind); } Patch::Remove(_) => { @@ -476,7 +483,8 @@ impl VTag { let input: Result = subject.clone().try_into(); if let Ok(input) = input { match change { - Patch::Add(kind, _) | Patch::Replace(kind, _) => { + Patch::Add(kind, _) | + Patch::Replace(kind, _) => { input.set_value(&kind); } Patch::Remove(_) => { @@ -535,7 +543,8 @@ macro_rules! impl_action { stringify!($action) } - fn attach(&mut self, element: &Element, messages: Messages) -> EventListenerHandle { + fn attach(&mut self, element: &Element, messages: Messages) + -> EventListenerHandle { let handler = self.0.take().unwrap(); let this = element.clone(); let sender = move |event: $type| { @@ -609,9 +618,7 @@ pub struct KeyData { impl From for KeyData { fn from(event: T) -> Self { - KeyData { - key: event.key(), - } + KeyData { key: event.key() } } } @@ -624,4 +631,3 @@ fn set_attribute(element: &Element, name: &str, value: &str) { fn remove_attribute(element: &Element, name: &str) { js!( @{element}.removeAttribute( @{name} ); ); } -