From d59716ba5b822e90ed307d8c8c9a2c581760e6ea Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Tue, 30 Oct 2018 02:39:07 +0000 Subject: [PATCH] Remove compiler warnings in todo example, simplify usage of elements into element.rs and fix a bug with focus not working on edit. --- examples/todomvc/Cargo.toml | 1 + examples/todomvc/src/controller.rs | 13 +-- examples/todomvc/src/element.rs | 181 +++++++++++++++++++++++++---- examples/todomvc/src/lib.rs | 3 +- examples/todomvc/src/store.rs | 16 ++- examples/todomvc/src/template.rs | 7 +- examples/todomvc/src/view.rs | 163 ++++++++++---------------- 7 files changed, 242 insertions(+), 142 deletions(-) diff --git a/examples/todomvc/Cargo.toml b/examples/todomvc/Cargo.toml index 5316dfd9..5ae330d1 100644 --- a/examples/todomvc/Cargo.toml +++ b/examples/todomvc/Cargo.toml @@ -13,6 +13,7 @@ askama = "0.7.2" js-sys = "0.3.5" wasm-bindgen = "0.2.28" askama = "0.7.2" +console_error_panic_hook = "0.1" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/todomvc/src/controller.rs b/examples/todomvc/src/controller.rs index 0bc30713..ebb0ccf4 100644 --- a/examples/todomvc/src/controller.rs +++ b/examples/todomvc/src/controller.rs @@ -140,14 +140,11 @@ impl Controller { /// Set all items to complete or active. fn toggle_all(&mut self, completed: bool) { let mut vals = Vec::new(); - self.store - .find( - ItemQuery::EmptyItemQuery, - ).map(|data| { - for item in data.iter() { - vals.push(item.id.clone()); - } - }); + self.store.find(ItemQuery::EmptyItemQuery).map(|data| { + for item in data.iter() { + vals.push(item.id.clone()); + } + }); for id in vals.iter() { self.toggle_completed(id.to_string(), completed); } diff --git a/examples/todomvc/src/element.rs b/examples/todomvc/src/element.rs index 8d073c8a..f14d6a09 100644 --- a/examples/todomvc/src/element.rs +++ b/examples/todomvc/src/element.rs @@ -1,12 +1,26 @@ extern crate wasm_bindgen; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; +use web_sys::EventTarget; /// Wrapper for `web_sys::Element` to simplify calling different interfaces pub struct Element { el: Option, } +impl From for Element { + fn from(el: web_sys::Element) -> Element { + Element { el: Some(el) } + } +} + +impl From for Element { + fn from(el: web_sys::EventTarget) -> Element { + let el = wasm_bindgen::JsCast::dyn_into::(el); + Element { el: el.ok() } + } +} + impl From for Option { fn from(obj: Element) -> Option { if let Some(el) = obj.el { @@ -17,8 +31,8 @@ impl From for Option { } } -impl From for Option { - fn from(obj: Element) -> Option { +impl From for Option { + fn from(obj: Element) -> Option { if let Some(el) = obj.el { Some(el.into()) } else { @@ -28,6 +42,15 @@ impl From for Option { } impl Element { + // Create an element from a tag name + pub fn create_element(tag: &str) -> Option { + if let Some(el) = web_sys::window()?.document()?.create_element(tag).ok() { + Some(el.into()) + } else { + None + } + } + pub fn qs(selector: &str) -> Option { let body: web_sys::Element = web_sys::window()?.document()?.body()?.into(); let el = body.query_selector(selector).ok()?; @@ -41,8 +64,10 @@ impl Element { { let cb = Closure::wrap(Box::new(handler) as Box); if let Some(el) = self.el.take() { - let el_et: web_sys::EventTarget = el.into(); - el_et.add_event_listener_with_callback(event_name, cb.as_ref().unchecked_ref()); + let el_et: EventTarget = el.into(); + el_et + .add_event_listener_with_callback(event_name, cb.as_ref().unchecked_ref()) + .unwrap(); cb.forget(); if let Ok(el) = el_et.dyn_into::() { self.el = Some(el); @@ -64,8 +89,7 @@ impl Element { Some(e) => e, None => return, }; - if let Some(dyn_el) = &el.dyn_ref::() - { + if let Some(dyn_el) = &el.dyn_ref::() { if let Some(window) = web_sys::window() { if let Some(document) = window.document() { // TODO document selector to the target element @@ -73,13 +97,10 @@ impl Element { let cb = Closure::wrap(Box::new(move |event: web_sys::Event| { if let Some(target_element) = event.target() { - let dyn_target_el: Option< - &web_sys::Node, - > = wasm_bindgen::JsCast::dyn_ref(&target_element); + let dyn_target_el: Option<&web_sys::Node> = + wasm_bindgen::JsCast::dyn_ref(&target_element); if let Some(target_element) = dyn_target_el { - if let Ok(potential_elements) = - tg_el.query_selector_all(selector) - { + if let Ok(potential_elements) = tg_el.query_selector_all(selector) { let mut has_match = false; for i in 0..potential_elements.length() { if let Some(el) = potential_elements.get(i) { @@ -98,11 +119,13 @@ impl Element { } }) as Box); - dyn_el.add_event_listener_with_callback_and_bool( - event, - cb.as_ref().unchecked_ref(), - use_capture, - ); + dyn_el + .add_event_listener_with_callback_and_bool( + event, + cb.as_ref().unchecked_ref(), + use_capture, + ) + .unwrap(); cb.forget(); // TODO cycle collect } } @@ -138,6 +161,43 @@ impl Element { } } + /// Gets the text content of the `self.el` element + pub fn text_content(&mut self) -> Option { + let mut text = None; + if let Some(el) = self.el.as_ref() { + if let Some(node) = &el.dyn_ref::() { + text = node.text_content(); + } + } + text + } + + /// Gets the parent of the `self.el` element + pub fn parent_element(&mut self) -> Option { + let mut parent = None; + if let Some(el) = self.el.as_ref() { + if let Some(node) = &el.dyn_ref::() { + if let Some(parent_node) = node.parent_element() { + parent = Some(parent_node.into()); + } + } + } + parent + } + + /// Gets the parent of the `self.el` element + pub fn append_child(&mut self, child: &mut Element) { + if let Some(el) = self.el.as_ref() { + if let Some(node) = &el.dyn_ref::() { + if let Some(ref child_el) = child.el { + if let Some(child_node) = child_el.dyn_ref::() { + node.append_child(child_node).unwrap(); + } + } + } + } + } + /// Removes a class list item from the element /// /// ``` @@ -146,7 +206,14 @@ impl Element { /// ``` pub fn class_list_remove(&mut self, value: &str) { if let Some(el) = self.el.take() { - el.class_list().remove_1(&value); + el.class_list().remove_1(&value).unwrap(); + self.el = Some(el); + } + } + + pub fn class_list_add(&mut self, value: &str) { + if let Some(el) = self.el.take() { + el.class_list().add_1(&value).unwrap(); self.el = Some(el); } } @@ -158,7 +225,7 @@ impl Element { if let Some(el) = self.el.take() { if let Some(el_node) = el.dyn_ref::() { let child_node: web_sys::Node = child_el.into(); - el_node.remove_child(&child_node); + el_node.remove_child(&child_node).unwrap(); } self.el = Some(el); } @@ -186,7 +253,57 @@ impl Element { } } - /// Sets the visibility for the element in `self.el` (The element must be an input) + pub fn blur(&mut self) { + if let Some(el) = self.el.take() { + { + let dyn_el: Option<&web_sys::HtmlElement> = wasm_bindgen::JsCast::dyn_ref(&el); + if let Some(el) = dyn_el { + // There isn't much we can do with the result here so ignore + el.blur().unwrap(); + } + } + self.el = Some(el); + } + } + + pub fn focus(&mut self) { + if let Some(el) = self.el.take() { + { + let dyn_el: Option<&web_sys::HtmlElement> = wasm_bindgen::JsCast::dyn_ref(&el); + if let Some(el) = dyn_el { + // There isn't much we can do with the result here so ignore + el.focus().unwrap(); + } + } + self.el = Some(el); + } + } + + pub fn dataset_set(&mut self, key: &str, value: &str) { + if let Some(el) = self.el.take() { + { + if let Some(el) = wasm_bindgen::JsCast::dyn_ref::(&el) { + el.dataset().set(key, value).unwrap(); + } + } + self.el = Some(el); + } + } + + pub fn dataset_get(&mut self, key: &str) -> String { + let mut text = String::new(); + if let Some(el) = self.el.take() { + { + if let Some(el) = wasm_bindgen::JsCast::dyn_ref::(&el) { + text = el.dataset().get(key); + } + } + self.el = Some(el); + } + text + } + + /// Sets the value for the element in `self.el` (The element must be an input) pub fn set_value(&mut self, value: &str) { if let Some(el) = self.el.take() { if let Some(el) = wasm_bindgen::JsCast::dyn_ref::(&el) { @@ -196,6 +313,18 @@ impl Element { } } + /// Gets the value for the element in `self.el` (The element must be an input) + pub fn value(&mut self) -> String { + let mut v = String::new(); + if let Some(el) = self.el.take() { + if let Some(el) = wasm_bindgen::JsCast::dyn_ref::(&el) { + v = el.value(); + } + self.el = Some(el); + } + v + } + /// Sets the checked state for the element in `self.el` (The element must be an input) pub fn set_checked(&mut self, checked: bool) { if let Some(el) = self.el.take() { @@ -205,4 +334,16 @@ impl Element { self.el = Some(el); } } + + /// Gets the checked state for the element in `self.el` (The element must be an input) + pub fn checked(&mut self) -> bool { + let mut checked = false; + if let Some(el) = self.el.take() { + if let Some(el) = wasm_bindgen::JsCast::dyn_ref::(&el) { + checked = el.checked(); + } + self.el = Some(el); + } + checked + } } diff --git a/examples/todomvc/src/lib.rs b/examples/todomvc/src/lib.rs index 83a4dcc7..0925cddb 100644 --- a/examples/todomvc/src/lib.rs +++ b/examples/todomvc/src/lib.rs @@ -1,7 +1,6 @@ //! # TODO MVC //! //! A [TODO MVC](https://todomvc.com/) implementation written using [web-sys](https://rustwasm.github.io/wasm-bindgen/web-sys/overview.html) -#![warn(missing_docs)] extern crate wasm_bindgen; use wasm_bindgen::prelude::*; @@ -11,6 +10,7 @@ extern crate web_sys; use std::rc::Rc; extern crate askama; +extern crate console_error_panic_hook; /// Controller of the program pub mod controller; @@ -65,5 +65,6 @@ fn app(name: &str) { /// Entry point into the program from JavaScript #[wasm_bindgen] pub fn run() { + console_error_panic_hook::set_once(); app("todos-wasmbindgen"); } diff --git a/examples/todomvc/src/store.rs b/examples/todomvc/src/store.rs index 61380cd0..979dd3ac 100644 --- a/examples/todomvc/src/store.rs +++ b/examples/todomvc/src/store.rs @@ -70,7 +70,8 @@ impl Store { if let Ok(storage_string) = JSON::stringify(&JsValue::from(array)) { let storage_string: String = storage_string.to_string().into(); self.local_storage - .set_item(&self.name, storage_string.as_str()); + .set_item(&self.name, storage_string.as_str()) + .unwrap(); } } @@ -82,7 +83,12 @@ impl Store { /// // data will contain items whose completed properties are true /// ``` pub fn find(&mut self, query: ItemQuery) -> Option { - Some(self.data.iter().filter(|todo| query.matches(*todo)).collect()) + Some( + self.data + .iter() + .filter(|todo| query.matches(*todo)) + .collect(), + ) } /// Update an item in the Store. @@ -161,12 +167,10 @@ pub struct ItemList { list: Vec, } impl ItemList { - fn into_iter(self) -> std::vec::IntoIter { - self.list.into_iter() - } fn retain(&mut self, f: F) where - F: FnMut(&Item) -> bool { + F: FnMut(&Item) -> bool, + { self.list.retain(f); } fn iter_mut(&mut self) -> std::slice::IterMut { diff --git a/examples/todomvc/src/template.rs b/examples/todomvc/src/template.rs index 3ed9f179..faf85baa 100644 --- a/examples/todomvc/src/template.rs +++ b/examples/todomvc/src/template.rs @@ -1,5 +1,5 @@ -use store::{ItemList, ItemListTrait, Item}; use askama::Template as AskamaTemplate; +use store::{ItemList, ItemListTrait}; #[derive(AskamaTemplate)] #[template(path = "row.html")] @@ -9,7 +9,6 @@ struct RowTemplate<'a> { completed: bool, } - #[derive(AskamaTemplate)] #[template(path = "itemsLeft.html")] struct ItemsLeftTemplate { @@ -46,9 +45,7 @@ impl Template { /// /// Returns the contents for an "items left" indicator pub fn item_counter(active_todos: usize) -> String { - let items_left = ItemsLeftTemplate { - active_todos - }; + let items_left = ItemsLeftTemplate { active_todos }; if let Ok(res) = items_left.render() { res } else { diff --git a/examples/todomvc/src/view.rs b/examples/todomvc/src/view.rs index 5482307b..88761051 100644 --- a/examples/todomvc/src/view.rs +++ b/examples/todomvc/src/view.rs @@ -1,6 +1,6 @@ use crate::controller::ControllerMessage; -use crate::exit; use crate::element::Element; +use crate::exit; use crate::store::ItemList; use crate::{Message, Scheduler}; use std::cell::RefCell; @@ -28,31 +28,19 @@ pub enum ViewMessage { EditItemDone(String, String), SetItemComplete(String, bool), } - -fn item_id(element: &web_sys::EventTarget) -> Option { - //TODO ugly reformat - let dyn_el: Option<&web_sys::Node> = wasm_bindgen::JsCast::dyn_ref(element); - if let Some(element_node) = dyn_el { - element_node.parent_node().map(|parent| { - let mut res = None; - if let Some(e) = wasm_bindgen::JsCast::dyn_ref::(&parent) { - if e.dataset().get("id") != "" { - res = Some(e.dataset().get("id")) - } - }; - if None == res { - if let Some(ep) = parent.parent_node() { - if let Some(dyn_el) = wasm_bindgen::JsCast::dyn_ref::(&ep) - { - res = Some(dyn_el.dataset().get("id")); - } - } +fn item_id(mut element: Element) -> Option { + element.parent_element().map(|mut parent| { + let mut res = None; + let parent_id = parent.dataset_get("id"); + if parent_id != "" { + res = Some(parent_id); + } else { + if let Some(mut ep) = parent.parent_element() { + res = Some(ep.dataset_get("id")); } - res.unwrap() - }) - } else { - None - } + } + res.unwrap() + }) } /// Presentation layer @@ -103,19 +91,16 @@ impl View { if let Some(location) = document.location() { if let Ok(hash) = location.hash() { if let Ok(sched) = &(sched.try_borrow_mut()) { - sched.add_message(Message::Controller(ControllerMessage::SetPage( - hash, - ))); + sched.add_message(Message::Controller(ControllerMessage::SetPage(hash))); } } } }) as Box); let window_et: web_sys::EventTarget = window.into(); - window_et.add_event_listener_with_callback( - "hashchange", - set_page.as_ref().unchecked_ref(), - ); + window_et + .add_event_listener_with_callback("hashchange", set_page.as_ref().unchecked_ref()) + .unwrap(); set_page.forget(); // Cycle collect this //self.callbacks.push((window_et, "hashchange".to_string(), set_page)); self.bind_add_item(); @@ -134,9 +119,7 @@ impl View { "dblclick", |e: web_sys::Event| { if let Some(target) = e.target() { - if let Ok(el) = wasm_bindgen::JsCast::dyn_into::(target) { - View::edit_item(el); - } + View::edit_item(target.into()); } }, false, @@ -144,27 +127,20 @@ impl View { } /// Put an item into edit mode. - fn edit_item(target: web_sys::Element) { - let target_node: web_sys::Node = target.into(); - if let Some(parent_element) = target_node.parent_element() { - let parent_node: web_sys::Node = parent_element.into(); - if let Some(list_item) = parent_node.parent_element() { - list_item.class_list().add_1("editing"); - if let Some(input) = create_element("input") { + fn edit_item(mut el: Element) { + if let Some(mut parent_element) = el.parent_element() { + if let Some(mut list_item) = parent_element.parent_element() { + list_item.class_list_add("editing"); + if let Some(mut input) = Element::create_element("input") { input.set_class_name("edit"); - let list_item_node: web_sys::Node = list_item.into(); - list_item_node.append_child(&input.into()); + if let Some(text) = el.text_content() { + input.set_value(&text); + } + list_item.append_child(&mut input); + input.focus(); } } } - if let Some(el) = wasm_bindgen::JsCast::dyn_ref::(&target_node) { - if let Some(input_el) = - wasm_bindgen::JsCast::dyn_ref::(&target_node) - { - input_el.set_value(&el.inner_text()); - } - el.focus(); - } } /// Used by scheduler to convert a `ViewMessage` into a function call on the `View` @@ -210,7 +186,6 @@ impl View { /// Set the number in the 'items left' display. fn set_items_left(&mut self, items_left: usize) { - // TODO what is items left? self.todo_item_counter .set_inner_html(Template::item_counter(items_left)); } @@ -335,7 +310,8 @@ impl View { "click", move |e: web_sys::Event| { if let Some(target) = e.target() { - if let Some(item_id) = item_id(&target) { + let el: Element = target.into(); + if let Some(item_id) = item_id(el) { if let Ok(sched) = &(sched.try_borrow_mut()) { sched.add_message(Message::Controller(ControllerMessage::RemoveItem( item_id, @@ -355,15 +331,13 @@ impl View { "click", move |e: web_sys::Event| { if let Some(target) = e.target() { - if let Some(input_el) = - wasm_bindgen::JsCast::dyn_ref::(&target) - { - if let Some(item_id) = item_id(&target) { - if let Ok(sched) = &(sched.try_borrow_mut()) { - sched.add_message(Message::Controller( - ControllerMessage::ToggleItem(item_id, input_el.checked()), - )); - } + let mut el: Element = target.into(); + let checked = el.checked(); + if let Some(item_id) = item_id(el) { + if let Ok(sched) = &(sched.try_borrow_mut()) { + sched.add_message(Message::Controller(ControllerMessage::ToggleItem( + item_id, checked, + ))); } } } @@ -380,25 +354,18 @@ impl View { "blur", move |e: web_sys::Event| { if let Some(target) = e.target() { - if let Some(target_el) = - wasm_bindgen::JsCast::dyn_ref::(&target) - { - if target_el.dataset().get("iscanceled") != "true" { - if let Some(input_el) = - wasm_bindgen::JsCast::dyn_ref::(&target) - { - if let Some(item) = item_id(&target) { - // TODO refactor back into fn - // Was: &self.add_message(ControllerMessage::SetPage(hash)); - if let Ok(sched) = &(sched.try_borrow_mut()) { - sched.add_message(Message::Controller( - ControllerMessage::EditItemSave(item, input_el.value()), - )); - } - - // TODO refactor back into fn - } + let mut target_el: Element = target.into(); + if target_el.dataset_get("iscancelled") != "true" { + let val = target_el.value(); + if let Some(item) = item_id(target_el) { + // TODO refactor back into fn + // Was: &self.add_message(ControllerMessage::SetPage(hash)); + if let Ok(sched) = &(sched.try_borrow_mut()) { + sched.add_message(Message::Controller( + ControllerMessage::EditItemSave(item, val), + )); } + // TODO refactor back into fn } } } @@ -414,11 +381,8 @@ impl View { if let Some(key_e) = wasm_bindgen::JsCast::dyn_ref::(&e) { if key_e.key_code() == ENTER_KEY { if let Some(target) = e.target() { - if let Some(el) = - wasm_bindgen::JsCast::dyn_ref::(&target) - { - el.blur(); - } + let mut el: Element = target.into(); + el.blur(); } } } @@ -436,14 +400,10 @@ impl View { if let Some(key_e) = wasm_bindgen::JsCast::dyn_ref::(&e) { if key_e.key_code() == ESCAPE_KEY { if let Some(target) = e.target() { - if let Some(el) = - wasm_bindgen::JsCast::dyn_ref::(&target) - { - el.dataset().set("iscanceled", "true"); - el.blur(); - } - - if let Some(item_id) = item_id(&target) { + let mut el: Element = target.into(); + el.dataset_set("iscanceled", "true"); + el.blur(); + if let Some(item_id) = item_id(el) { if let Ok(sched) = &(sched.try_borrow_mut()) { sched.add_message(Message::Controller( ControllerMessage::EditItemCancel(item_id), @@ -459,20 +419,19 @@ impl View { } } -fn create_element(tag: &str) -> Option { - web_sys::window()?.document()?.create_element(tag).ok() -} - impl Drop for View { fn drop(&mut self) { exit("calling drop on view"); let callbacks: Vec<(web_sys::EventTarget, String, Closure)> = self.callbacks.drain(..).collect(); for callback in callbacks { - callback.0.remove_event_listener_with_callback( - callback.1.as_str(), - &callback.2.as_ref().unchecked_ref(), - ); + callback + .0 + .remove_event_listener_with_callback( + callback.1.as_str(), + &callback.2.as_ref().unchecked_ref(), + ) + .unwrap(); } } }