Adding in interning system for strings
This commit is contained in:
parent
ec85fef904
commit
fae50b2401
32
Cargo.toml
32
Cargo.toml
|
@ -30,36 +30,36 @@ features = ["futures_0_3"]
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3.22"
|
version = "0.3.22"
|
||||||
features = [
|
features = [
|
||||||
"CharacterData",
|
#"CharacterData",
|
||||||
"Comment",
|
"Comment",
|
||||||
"CssRule",
|
#"CssRule",
|
||||||
"CssRuleList",
|
#"CssRuleList",
|
||||||
"CssStyleDeclaration",
|
#"CssStyleDeclaration",
|
||||||
"CssStyleRule",
|
"CssStyleRule",
|
||||||
"CssStyleSheet",
|
"CssStyleSheet",
|
||||||
"Document",
|
#"Document",
|
||||||
"DocumentFragment",
|
#"DocumentFragment",
|
||||||
"DomTokenList",
|
#"DomTokenList",
|
||||||
"Element",
|
"Element",
|
||||||
"Event",
|
#"Event",
|
||||||
"EventTarget",
|
#"EventTarget",
|
||||||
"FocusEvent",
|
"FocusEvent",
|
||||||
"History",
|
#"History",
|
||||||
"InputEvent",
|
"InputEvent",
|
||||||
"HtmlElement",
|
"HtmlElement",
|
||||||
"HtmlHeadElement",
|
#"HtmlHeadElement",
|
||||||
"HtmlInputElement",
|
"HtmlInputElement",
|
||||||
"HtmlStyleElement",
|
#"HtmlStyleElement",
|
||||||
"HtmlTextAreaElement",
|
"HtmlTextAreaElement",
|
||||||
"KeyboardEvent",
|
"KeyboardEvent",
|
||||||
"Location",
|
#"Location",
|
||||||
"MouseEvent",
|
"MouseEvent",
|
||||||
"Node",
|
"Node",
|
||||||
"NodeList",
|
#"NodeList",
|
||||||
"StyleSheet",
|
#"StyleSheet",
|
||||||
"SvgElement",
|
"SvgElement",
|
||||||
"Text",
|
"Text",
|
||||||
"Url",
|
#"Url",
|
||||||
"Window",
|
"Window",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,7 @@ impl Drop for State {
|
||||||
|
|
||||||
// TODO move this into gloo
|
// TODO move this into gloo
|
||||||
fn set_interval<F>(ms: i32, f: F) where F: FnMut() + 'static {
|
fn set_interval<F>(ms: i32, f: F) where F: FnMut() + 'static {
|
||||||
let f = wasm_bindgen::closure::Closure::wrap(Box::new(f) as Box<FnMut()>);
|
let f = wasm_bindgen::closure::Closure::wrap(Box::new(f) as Box<dyn FnMut()>);
|
||||||
|
|
||||||
web_sys::window()
|
web_sys::window()
|
||||||
.unwrap_throw()
|
.unwrap_throw()
|
||||||
|
|
|
@ -18,10 +18,7 @@ module.exports = {
|
||||||
liveReload: true,
|
liveReload: true,
|
||||||
open: true,
|
open: true,
|
||||||
noInfo: true,
|
noInfo: true,
|
||||||
overlay: {
|
overlay: true
|
||||||
warnings: true,
|
|
||||||
errors: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin([
|
new CopyPlugin([
|
||||||
|
@ -29,7 +26,8 @@ module.exports = {
|
||||||
]),
|
]),
|
||||||
|
|
||||||
new WasmPackPlugin({
|
new WasmPackPlugin({
|
||||||
crateDirectory: __dirname
|
crateDirectory: __dirname,
|
||||||
|
extraArgs: "--out-name index"
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,10 +18,7 @@ module.exports = {
|
||||||
liveReload: true,
|
liveReload: true,
|
||||||
open: true,
|
open: true,
|
||||||
noInfo: true,
|
noInfo: true,
|
||||||
overlay: {
|
overlay: true
|
||||||
warnings: true,
|
|
||||||
errors: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin([
|
new CopyPlugin([
|
||||||
|
@ -29,7 +26,8 @@ module.exports = {
|
||||||
]),
|
]),
|
||||||
|
|
||||||
new WasmPackPlugin({
|
new WasmPackPlugin({
|
||||||
crateDirectory: __dirname
|
crateDirectory: __dirname,
|
||||||
|
extraArgs: "--out-name index"
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,6 +29,7 @@ features = ["rc"]
|
||||||
version = "0.3.22"
|
version = "0.3.22"
|
||||||
features = [
|
features = [
|
||||||
"Storage",
|
"Storage",
|
||||||
|
"Url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[target."cfg(debug_assertions)".dependencies]
|
[target."cfg(debug_assertions)".dependencies]
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::cell::Cell;
|
||||||
|
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use serde_derive::{Serialize, Deserialize};
|
use serde_derive::{Serialize, Deserialize};
|
||||||
use web_sys::{window, HtmlElement, Storage};
|
use web_sys::{window, HtmlElement, Storage, Url};
|
||||||
use futures_signals::map_ref;
|
use futures_signals::map_ref;
|
||||||
use futures_signals::signal::{Signal, SignalExt, Mutable};
|
use futures_signals::signal::{Signal, SignalExt, Mutable};
|
||||||
use futures_signals::signal_vec::{SignalVecExt, MutableVec};
|
use futures_signals::signal_vec::{SignalVecExt, MutableVec};
|
||||||
|
@ -37,7 +37,9 @@ enum Filter {
|
||||||
|
|
||||||
impl Filter {
|
impl Filter {
|
||||||
fn signal() -> impl Signal<Item = Self> {
|
fn signal() -> impl Signal<Item = Self> {
|
||||||
routing::url().map(|url| {
|
routing::url()
|
||||||
|
.signal_ref(|url| Url::new(&url).unwrap_throw())
|
||||||
|
.map(|url| {
|
||||||
match url.hash().as_str() {
|
match url.hash().as_str() {
|
||||||
"#/active" => Filter::Active,
|
"#/active" => Filter::Active,
|
||||||
"#/completed" => Filter::Completed,
|
"#/completed" => Filter::Completed,
|
||||||
|
|
|
@ -18,10 +18,7 @@ module.exports = {
|
||||||
liveReload: true,
|
liveReload: true,
|
||||||
open: true,
|
open: true,
|
||||||
noInfo: true,
|
noInfo: true,
|
||||||
overlay: {
|
overlay: true
|
||||||
warnings: true,
|
|
||||||
errors: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyPlugin([
|
new CopyPlugin([
|
||||||
|
@ -29,7 +26,8 @@ module.exports = {
|
||||||
]),
|
]),
|
||||||
|
|
||||||
new WasmPackPlugin({
|
new WasmPackPlugin({
|
||||||
crateDirectory: __dirname
|
crateDirectory: __dirname,
|
||||||
|
extraArgs: "--out-name index"
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
@ -374,7 +374,7 @@ impl<A, F, S> SignalVec for AnimatedMap<S, F>
|
||||||
},
|
},
|
||||||
|
|
||||||
VecDiff::UpdateAt { index, value } => {
|
VecDiff::UpdateAt { index, value } => {
|
||||||
let index = self.find_index(index).expect("Could not find value");
|
let index = self.find_index(index).unwrap_throw();
|
||||||
let state = {
|
let state = {
|
||||||
let state = &self.as_mut().animations()[index];
|
let state = &self.as_mut().animations()[index];
|
||||||
AnimatedMapBroadcaster(state.animation.raw_clone())
|
AnimatedMapBroadcaster(state.animation.raw_clone())
|
||||||
|
@ -386,7 +386,7 @@ impl<A, F, S> SignalVec for AnimatedMap<S, F>
|
||||||
// TODO test this
|
// TODO test this
|
||||||
// TODO should this be treated as a removal + insertion ?
|
// TODO should this be treated as a removal + insertion ?
|
||||||
VecDiff::Move { old_index, new_index } => {
|
VecDiff::Move { old_index, new_index } => {
|
||||||
let old_index = self.find_index(old_index).expect("Could not find value");
|
let old_index = self.find_index(old_index).unwrap_throw();
|
||||||
|
|
||||||
let state = self.as_mut().animations().remove(old_index);
|
let state = self.as_mut().animations().remove(old_index);
|
||||||
|
|
||||||
|
@ -398,7 +398,7 @@ impl<A, F, S> SignalVec for AnimatedMap<S, F>
|
||||||
},
|
},
|
||||||
|
|
||||||
VecDiff::RemoveAt { index } => {
|
VecDiff::RemoveAt { index } => {
|
||||||
let index = self.find_index(index).expect("Could not find value");
|
let index = self.find_index(index).unwrap_throw();
|
||||||
|
|
||||||
if self.as_mut().should_remove(cx, index) {
|
if self.as_mut().should_remove(cx, index) {
|
||||||
self.remove_index(index)
|
self.remove_index(index)
|
||||||
|
@ -409,7 +409,7 @@ impl<A, F, S> SignalVec for AnimatedMap<S, F>
|
||||||
},
|
},
|
||||||
|
|
||||||
VecDiff::Pop {} => {
|
VecDiff::Pop {} => {
|
||||||
let index = self.find_last_index().expect("Cannot pop from empty vec");
|
let index = self.find_last_index().unwrap_throw();
|
||||||
|
|
||||||
if self.as_mut().should_remove(cx, index) {
|
if self.as_mut().should_remove(cx, index) {
|
||||||
self.remove_index(index)
|
self.remove_index(index)
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use js_sys::JsString;
|
||||||
|
use web_sys::{HtmlElement, Element, Node, Window, Text, Comment, CssStyleSheet, CssStyleRule};
|
||||||
|
|
||||||
|
use crate::cache::intern;
|
||||||
|
|
||||||
|
|
||||||
|
#[wasm_bindgen(inline_js = "
|
||||||
|
export function body() { return document.body; }
|
||||||
|
export function _window() { return window; }
|
||||||
|
|
||||||
|
export function ready_state() { return document.readyState; }
|
||||||
|
|
||||||
|
export function current_url() { return location.href; }
|
||||||
|
export function go_to_url(url) { history.pushState(null, \"\", url); }
|
||||||
|
|
||||||
|
export function create_stylesheet() {
|
||||||
|
// TODO use createElementNS ?
|
||||||
|
var e = document.createElement(\"style\");
|
||||||
|
e.type = \"text/css\";
|
||||||
|
document.head.appendChild(e);
|
||||||
|
return e.sheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function make_style_rule(sheet, selector) {
|
||||||
|
var rules = sheet.cssRules;
|
||||||
|
var length = rules.length;
|
||||||
|
sheet.insertRule(selector + \" {}\", length);
|
||||||
|
return rules[length];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function create_element(name) { return document.createElement(name); }
|
||||||
|
export function create_element_ns(namespace, name) { return document.createElementNS(namespace, name); }
|
||||||
|
|
||||||
|
export function create_text_node(value) { return document.createTextNode(value); }
|
||||||
|
|
||||||
|
// http://jsperf.com/textnode-performance
|
||||||
|
export function set_text(elem, value) { elem.data = value; }
|
||||||
|
|
||||||
|
export function create_comment(value) { return document.createComment(value); }
|
||||||
|
|
||||||
|
export function set_attribute(elem, key, value) { elem.setAttribute(key, value); }
|
||||||
|
export function set_attribute_ns(elem, namespace, key, value) { elem.setAttributeNS(namespace, key, value); }
|
||||||
|
|
||||||
|
export function remove_attribute(elem, key) { elem.removeAttribute(key); }
|
||||||
|
export function remove_attribute_ns(elem, namespace, key) { elem.removeAttributeNS(namespace, key); }
|
||||||
|
|
||||||
|
export function add_class(elem, value) { elem.classList.add(value); }
|
||||||
|
export function remove_class(elem, value) { elem.classList.remove(value); }
|
||||||
|
|
||||||
|
export function set_text_content(elem, value) { elem.textContent = value; }
|
||||||
|
|
||||||
|
export function get_style(elem, name) { return elem.style.getPropertyValue(name); }
|
||||||
|
export function remove_style(elem, name) { return elem.style.removeProperty(name); }
|
||||||
|
|
||||||
|
export function set_style(elem, name, value, important) {
|
||||||
|
elem.style.setProperty(name, value, (important ? \"important\" : \"\"));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get_at(parent, index) { return parent.childNodes[index]; }
|
||||||
|
export function insert_child_before(parent, child, other) { parent.insertBefore(child, other); }
|
||||||
|
export function replace_child(parent, child, other) { parent.replaceChild(child, other); }
|
||||||
|
export function append_child(parent, child) { parent.appendChild(child); }
|
||||||
|
export function remove_child(parent, child) { parent.removeChild(child); }
|
||||||
|
|
||||||
|
export function focus(elem) { elem.focus(); }
|
||||||
|
export function blur(elem) { elem.blur(); }
|
||||||
|
|
||||||
|
export function set_property(obj, name, value) { obj[name] = value; }
|
||||||
|
")]
|
||||||
|
extern "C" {
|
||||||
|
pub(crate) fn body() -> HtmlElement;
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = _window)]
|
||||||
|
pub(crate) fn window() -> Window;
|
||||||
|
|
||||||
|
pub(crate) fn ready_state() -> JsString;
|
||||||
|
|
||||||
|
pub(crate) fn current_url() -> JsString;
|
||||||
|
pub(crate) fn go_to_url(url: &JsString);
|
||||||
|
|
||||||
|
pub(crate) fn create_stylesheet() -> CssStyleSheet;
|
||||||
|
pub(crate) fn make_style_rule(sheet: &CssStyleSheet, selector: &JsString) -> CssStyleRule;
|
||||||
|
|
||||||
|
pub(crate) fn create_element(name: &JsString) -> Element;
|
||||||
|
pub(crate) fn create_element_ns(namespace: &JsString, name: &JsString) -> Element;
|
||||||
|
|
||||||
|
pub(crate) fn create_text_node(value: &JsString) -> Text;
|
||||||
|
pub(crate) fn set_text(elem: &Text, value: &JsString);
|
||||||
|
|
||||||
|
pub(crate) fn create_comment(value: &JsString) -> Comment;
|
||||||
|
|
||||||
|
// TODO check that the attribute *actually* was changed
|
||||||
|
pub(crate) fn set_attribute(elem: &Element, key: &JsString, value: &JsString);
|
||||||
|
pub(crate) fn set_attribute_ns(elem: &Element, namespace: &JsString, key: &JsString, value: &JsString);
|
||||||
|
|
||||||
|
pub(crate) fn remove_attribute(elem: &Element, key: &JsString);
|
||||||
|
pub(crate) fn remove_attribute_ns(elem: &Element, namespace: &JsString, key: &JsString);
|
||||||
|
|
||||||
|
pub(crate) fn add_class(elem: &Element, value: &JsString);
|
||||||
|
pub(crate) fn remove_class(elem: &Element, value: &JsString);
|
||||||
|
|
||||||
|
pub(crate) fn set_text_content(elem: &Node, value: &JsString);
|
||||||
|
|
||||||
|
// TODO better type for elem
|
||||||
|
pub(crate) fn get_style(elem: &JsValue, name: &JsString) -> JsString;
|
||||||
|
pub(crate) fn remove_style(elem: &JsValue, name: &JsString);
|
||||||
|
pub(crate) fn set_style(elem: &JsValue, name: &JsString, value: &JsString, important: bool);
|
||||||
|
|
||||||
|
pub(crate) fn get_at(parent: &Node, index: u32) -> Node;
|
||||||
|
pub(crate) fn insert_child_before(parent: &Node, child: &Node, other: &Node);
|
||||||
|
pub(crate) fn replace_child(parent: &Node, child: &Node, other: &Node);
|
||||||
|
pub(crate) fn append_child(parent: &Node, child: &Node);
|
||||||
|
pub(crate) fn remove_child(parent: &Node, child: &Node);
|
||||||
|
|
||||||
|
pub(crate) fn focus(elem: &HtmlElement);
|
||||||
|
pub(crate) fn blur(elem: &HtmlElement);
|
||||||
|
|
||||||
|
// TODO maybe use Object for obj ?
|
||||||
|
pub(crate) fn set_property(obj: &JsValue, name: &JsString, value: &JsValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn remove_all_children(node: &Node) {
|
||||||
|
set_text_content(node, &intern(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO make this more efficient
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn insert_at(parent: &Node, index: u32, child: &Node) {
|
||||||
|
insert_child_before(parent, child, &get_at(parent, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO make this more efficient
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn update_at(parent: &Node, index: u32, child: &Node) {
|
||||||
|
replace_child(parent, child, &get_at(parent, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO make this more efficient
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn remove_at(parent: &Node, index: u32) {
|
||||||
|
remove_child(parent, &get_at(parent, index));
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use js_sys::JsString;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
// TODO is it possible to avoid the RefCell ?
|
||||||
|
static CACHE: RefCell<HashMap<String, JsString>> = RefCell::new(HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO make this more efficient
|
||||||
|
pub(crate) fn intern(x: &str) -> JsString {
|
||||||
|
CACHE.with(|cache| {
|
||||||
|
let mut cache = cache.borrow_mut();
|
||||||
|
|
||||||
|
match cache.get(x) {
|
||||||
|
Some(value) => value.clone(),
|
||||||
|
None => {
|
||||||
|
let js = JsString::from(x);
|
||||||
|
cache.insert(x.to_owned(), js.clone());
|
||||||
|
js
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn debug_cache() {
|
||||||
|
CACHE.with(|cache| {
|
||||||
|
let cache = cache.borrow();
|
||||||
|
web_sys::console::log_1(&wasm_bindgen::JsValue::from(format!("{:#?}", cache.keys())));
|
||||||
|
});
|
||||||
|
}
|
189
src/dom.rs
189
src/dom.rs
|
@ -12,16 +12,17 @@ use futures_util::FutureExt;
|
||||||
use futures_channel::oneshot;
|
use futures_channel::oneshot;
|
||||||
use discard::{Discard, DiscardOnDrop};
|
use discard::{Discard, DiscardOnDrop};
|
||||||
use wasm_bindgen::{JsValue, UnwrapThrowExt, JsCast};
|
use wasm_bindgen::{JsValue, UnwrapThrowExt, JsCast};
|
||||||
use js_sys::Reflect;
|
use js_sys::JsString;
|
||||||
use web_sys::{window, HtmlElement, Node, EventTarget, Element, CssStyleSheet, HtmlStyleElement, CssStyleRule, CssStyleDeclaration};
|
use web_sys::{HtmlElement, Node, EventTarget, Element, CssStyleSheet, CssStyleRule};
|
||||||
use gloo::events::{EventListener, EventListenerOptions};
|
use gloo::events::{EventListener, EventListenerOptions};
|
||||||
|
|
||||||
|
use crate::cache::intern;
|
||||||
|
use crate::bindings;
|
||||||
use crate::callbacks::Callbacks;
|
use crate::callbacks::Callbacks;
|
||||||
use crate::traits::*;
|
use crate::traits::*;
|
||||||
use crate::operations;
|
use crate::operations;
|
||||||
use crate::operations::{for_each, spawn_future};
|
use crate::operations::{for_each, spawn_future};
|
||||||
use crate::dom_operations;
|
use crate::utils::{on, on_with_options, ValueDiscard, FnDiscard, EventDiscard};
|
||||||
use crate::utils::{document, on, on_with_options, ValueDiscard, FnDiscard, EventDiscard};
|
|
||||||
|
|
||||||
|
|
||||||
pub struct RefFn<A, B, C> where B: ?Sized {
|
pub struct RefFn<A, B, C> where B: ?Sized {
|
||||||
|
@ -92,7 +93,7 @@ lazy_static! {
|
||||||
|
|
||||||
// TODO should return HtmlBodyElement ?
|
// TODO should return HtmlBodyElement ?
|
||||||
pub fn body() -> HtmlElement {
|
pub fn body() -> HtmlElement {
|
||||||
document().body().unwrap_throw()
|
bindings::body()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,14 +105,14 @@ pub struct DomHandle {
|
||||||
impl Discard for DomHandle {
|
impl Discard for DomHandle {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn discard(self) {
|
fn discard(self) {
|
||||||
self.parent.remove_child(&self.dom.element).unwrap_throw();
|
bindings::remove_child(&self.parent, &self.dom.element);
|
||||||
self.dom.callbacks.discard();
|
self.dom.callbacks.discard();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn append_dom(parent: &Node, mut dom: Dom) -> DomHandle {
|
pub fn append_dom(parent: &Node, mut dom: Dom) -> DomHandle {
|
||||||
parent.append_child(&dom.element).unwrap_throw();
|
bindings::append_child(&parent, &dom.element);
|
||||||
|
|
||||||
dom.callbacks.trigger_after_insert();
|
dom.callbacks.trigger_after_insert();
|
||||||
|
|
||||||
|
@ -141,7 +142,7 @@ impl Signal for IsWindowLoaded {
|
||||||
fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||||
let result = match *self {
|
let result = match *self {
|
||||||
IsWindowLoaded::Initial {} => {
|
IsWindowLoaded::Initial {} => {
|
||||||
let is_ready = document().ready_state() == "complete";
|
let is_ready = bindings::ready_state() == "complete";
|
||||||
|
|
||||||
if is_ready {
|
if is_ready {
|
||||||
Poll::Ready(Some(true))
|
Poll::Ready(Some(true))
|
||||||
|
@ -151,7 +152,7 @@ impl Signal for IsWindowLoaded {
|
||||||
|
|
||||||
*self = IsWindowLoaded::Pending {
|
*self = IsWindowLoaded::Pending {
|
||||||
receiver,
|
receiver,
|
||||||
_event: EventListener::once(&window().unwrap_throw(), "load", move |_| {
|
_event: EventListener::once(&bindings::window(), "load", move |_| {
|
||||||
// TODO test this
|
// TODO test this
|
||||||
sender.send(Some(true)).unwrap_throw();
|
sender.send(Some(true)).unwrap_throw();
|
||||||
}),
|
}),
|
||||||
|
@ -185,7 +186,7 @@ pub fn is_window_loaded() -> impl Signal<Item = bool> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn text(value: &str) -> Dom {
|
pub fn text(value: &str) -> Dom {
|
||||||
Dom::new(document().create_text_node(value).into())
|
Dom::new(bindings::create_text_node(&intern(value)).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -194,7 +195,7 @@ pub fn text_signal<A, B>(value: B) -> Dom
|
||||||
where A: AsStr,
|
where A: AsStr,
|
||||||
B: Signal<Item = A> + 'static {
|
B: Signal<Item = A> + 'static {
|
||||||
|
|
||||||
let element = document().create_text_node("");
|
let element = bindings::create_text_node(&intern(""));
|
||||||
|
|
||||||
let mut callbacks = Callbacks::new();
|
let mut callbacks = Callbacks::new();
|
||||||
|
|
||||||
|
@ -204,8 +205,8 @@ pub fn text_signal<A, B>(value: B) -> Dom
|
||||||
callbacks.after_remove(for_each(value, move |value| {
|
callbacks.after_remove(for_each(value, move |value| {
|
||||||
let value = value.as_str();
|
let value = value.as_str();
|
||||||
|
|
||||||
// http://jsperf.com/textnode-performance
|
// TODO maybe this should intern ?
|
||||||
element.set_data(value);
|
bindings::set_text(&element, &JsString::from(value));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +237,7 @@ impl Dom {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
// TODO is there a better way of doing this ?
|
// TODO is there a better way of doing this ?
|
||||||
Self::new(document().create_comment("").into())
|
Self::new(bindings::create_comment(&intern("")).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -255,12 +256,12 @@ impl Dom {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn create_element<A>(name: &str) -> A where A: JsCast {
|
pub fn create_element<A>(name: &str) -> A where A: JsCast {
|
||||||
document().create_element(name).unwrap_throw().dyn_into().unwrap_throw()
|
bindings::create_element(&intern(name)).dyn_into().unwrap_throw()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn create_element_ns<A>(name: &str, namespace: &str) -> A where A: JsCast {
|
pub fn create_element_ns<A>(name: &str, namespace: &str) -> A where A: JsCast {
|
||||||
document().create_element_ns(Some(namespace), name).unwrap_throw().dyn_into().unwrap_throw()
|
bindings::create_element_ns(&intern(namespace), &intern(name)).dyn_into().unwrap_throw()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -289,37 +290,50 @@ fn set_option<A, B, C, D, F>(element: A, callbacks: &mut Callbacks, value: D, mu
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_style<A, B>(style: &CssStyleDeclaration, name: &A, value: B, important: bool)
|
fn set_style<A, B>(element: &JsValue, name: &A, value: B, important: bool, should_intern: bool)
|
||||||
where A: MultiStr,
|
where A: MultiStr,
|
||||||
B: MultiStr {
|
B: MultiStr {
|
||||||
|
|
||||||
let mut names = vec![];
|
let mut names = vec![];
|
||||||
let mut values = vec![];
|
let mut values = vec![];
|
||||||
|
|
||||||
fn try_set_style(style: &CssStyleDeclaration, names: &mut Vec<String>, values: &mut Vec<String>, name: &str, value: &str, important: bool) -> Result<bool, JsValue> {
|
fn try_set_style(element: &JsValue, names: &mut Vec<String>, values: &mut Vec<String>, name: &JsString, value: &JsString, important: bool) -> bool {
|
||||||
assert!(value != "");
|
// TODO move this out of this function ?
|
||||||
|
let empty = intern("");
|
||||||
|
|
||||||
|
assert!(*value != empty);
|
||||||
|
|
||||||
// TODO handle browser prefixes ?
|
// TODO handle browser prefixes ?
|
||||||
style.remove_property(name)?;
|
bindings::remove_style(element, name);
|
||||||
|
|
||||||
style.set_property_with_priority(name, value, if important { "important" } else { "" })?;
|
bindings::set_style(element, name, value, important);
|
||||||
|
|
||||||
// TODO maybe use cfg(debug_assertions) ?
|
// TODO maybe use cfg(debug_assertions) ?
|
||||||
let is_changed = style.get_property_value(name)? != "";
|
let is_changed = bindings::get_style(element, name) != empty;
|
||||||
|
|
||||||
if is_changed {
|
if is_changed {
|
||||||
Ok(true)
|
true
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
names.push(name.to_string());
|
names.push(String::from(name));
|
||||||
values.push(value.to_string());
|
values.push(String::from(value));
|
||||||
Ok(false)
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let okay = name.any(|name| {
|
let okay = name.any(|name| {
|
||||||
|
let name = intern(name);
|
||||||
|
|
||||||
value.any(|value| {
|
value.any(|value| {
|
||||||
try_set_style(style, &mut names, &mut values, name, value, important).unwrap_throw()
|
// TODO maybe always intern the value ?
|
||||||
|
let value = if should_intern {
|
||||||
|
intern(value)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
JsString::from(value)
|
||||||
|
};
|
||||||
|
|
||||||
|
try_set_style(element, &mut names, &mut values, &name, &value, important)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -329,21 +343,24 @@ fn set_style<A, B>(style: &CssStyleDeclaration, name: &A, value: B, important: b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_style_signal<A, B, C, D>(style: CssStyleDeclaration, callbacks: &mut Callbacks, name: A, value: D, important: bool)
|
fn set_style_signal<A, B, C, D>(element: &JsValue, callbacks: &mut Callbacks, name: A, value: D, important: bool)
|
||||||
where A: MultiStr + 'static,
|
where A: MultiStr + 'static,
|
||||||
B: MultiStr,
|
B: MultiStr,
|
||||||
C: OptionStr<Output = B>,
|
C: OptionStr<Output = B>,
|
||||||
D: Signal<Item = C> + 'static {
|
D: Signal<Item = C> + 'static {
|
||||||
|
|
||||||
set_option(style, callbacks, value, move |style, value| {
|
// TODO verify that this is always a cheap O(1) clone
|
||||||
|
let element = element.clone();
|
||||||
|
|
||||||
|
set_option(element, callbacks, value, move |element, value| {
|
||||||
match value {
|
match value {
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
set_style(style, &name, value, important);
|
set_style(element, &name, value, important, false);
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
name.each(|name| {
|
name.each(|name| {
|
||||||
// TODO handle browser prefixes ?
|
// TODO handle browser prefixes ?
|
||||||
style.remove_property(name).unwrap_throw();
|
bindings::remove_style(element, &intern(name));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -351,13 +368,13 @@ fn set_style_signal<A, B, C, D>(style: CssStyleDeclaration, callbacks: &mut Call
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO check that the property *actually* was changed ?
|
// TODO check that the property *actually* was changed ?
|
||||||
|
// TODO maybe use AsRef<Object> ?
|
||||||
fn set_property<A, B, C>(element: &A, name: &B, value: C) where A: AsRef<JsValue>, B: MultiStr, C: Into<JsValue> {
|
fn set_property<A, B, C>(element: &A, name: &B, value: C) where A: AsRef<JsValue>, B: MultiStr, C: Into<JsValue> {
|
||||||
let element = element.as_ref();
|
let element = element.as_ref();
|
||||||
let value = value.into();
|
let value = value.into();
|
||||||
|
|
||||||
name.each(|name| {
|
name.each(|name| {
|
||||||
// TODO can this be made more efficient ?
|
bindings::set_property(element, &intern(name), &value);
|
||||||
assert!(Reflect::set(element, &JsValue::from(name), &value).unwrap_throw());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,7 +417,7 @@ impl<A> DomBuilder<A> {
|
||||||
pub fn global_event<T, F>(mut self, listener: F) -> Self
|
pub fn global_event<T, F>(mut self, listener: F) -> Self
|
||||||
where T: StaticEvent,
|
where T: StaticEvent,
|
||||||
F: FnMut(T) + 'static {
|
F: FnMut(T) + 'static {
|
||||||
self._event(&window().unwrap_throw(), listener);
|
self._event(&bindings::window(), listener);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -531,14 +548,14 @@ impl<A> DomBuilder<A> where A: AsRef<Node> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn children<'a, B: IntoIterator<Item = &'a mut Dom>>(mut self, children: B) -> Self {
|
pub fn children<'a, B: IntoIterator<Item = &'a mut Dom>>(mut self, children: B) -> Self {
|
||||||
self.check_children();
|
self.check_children();
|
||||||
operations::insert_children_iter(self.element.as_ref(), &mut self.callbacks, children).unwrap_throw();
|
operations::insert_children_iter(self.element.as_ref(), &mut self.callbacks, children);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn text(mut self, value: &str) -> Self {
|
pub fn text(mut self, value: &str) -> Self {
|
||||||
self.check_children();
|
self.check_children();
|
||||||
self.element.as_ref().set_text_content(Some(value));
|
bindings::set_text_content(self.element.as_ref(), &intern(value));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,7 +567,8 @@ impl<A> DomBuilder<A> where A: AsRef<Node> {
|
||||||
|
|
||||||
self.callbacks.after_remove(for_each(value, move |value| {
|
self.callbacks.after_remove(for_each(value, move |value| {
|
||||||
let value = value.as_str();
|
let value = value.as_str();
|
||||||
element.set_text_content(Some(value));
|
// TODO maybe intern this ?
|
||||||
|
bindings::set_text_content(&element, &JsString::from(value));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -581,26 +599,35 @@ impl<A> DomBuilder<A> where A: AsRef<Node> {
|
||||||
impl<A> DomBuilder<A> where A: AsRef<Element> {
|
impl<A> DomBuilder<A> where A: AsRef<Element> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn attribute<B>(self, name: B, value: &str) -> Self where B: MultiStr {
|
pub fn attribute<B>(self, name: B, value: &str) -> Self where B: MultiStr {
|
||||||
|
let element = self.element.as_ref();
|
||||||
|
let value = intern(value);
|
||||||
|
|
||||||
name.each(|name| {
|
name.each(|name| {
|
||||||
dom_operations::set_attribute(self.element.as_ref(), name, value).unwrap_throw();
|
bindings::set_attribute(element, &intern(name), &value);
|
||||||
});
|
});
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn attribute_namespace<B>(self, namespace: &str, name: B, value: &str) -> Self where B: MultiStr {
|
pub fn attribute_namespace<B>(self, namespace: &str, name: B, value: &str) -> Self where B: MultiStr {
|
||||||
|
let element = self.element.as_ref();
|
||||||
|
let namespace = intern(namespace);
|
||||||
|
let value = intern(value);
|
||||||
|
|
||||||
name.each(|name| {
|
name.each(|name| {
|
||||||
dom_operations::set_attribute_ns(self.element.as_ref(), namespace, name, value).unwrap_throw();
|
bindings::set_attribute_ns(element, &namespace, &intern(name), &value);
|
||||||
});
|
});
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn class<B>(self, name: B) -> Self where B: MultiStr {
|
pub fn class<B>(self, name: B) -> Self where B: MultiStr {
|
||||||
let list = self.element.as_ref().class_list();
|
let element = self.element.as_ref();
|
||||||
|
|
||||||
name.each(|name| {
|
name.each(|name| {
|
||||||
dom_operations::add_class(&list, name).unwrap_throw();
|
bindings::add_class(element, &intern(name));
|
||||||
});
|
});
|
||||||
|
|
||||||
self
|
self
|
||||||
|
@ -630,14 +657,16 @@ impl<A> DomBuilder<A> where A: AsRef<Element> {
|
||||||
match value {
|
match value {
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
let value = value.as_str();
|
let value = value.as_str();
|
||||||
|
// TODO should this intern ?
|
||||||
|
let value = intern(value);
|
||||||
|
|
||||||
name.each(|name| {
|
name.each(|name| {
|
||||||
dom_operations::set_attribute(element, &name, value).unwrap_throw();
|
bindings::set_attribute(element, &intern(name), &value);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
name.each(|name| {
|
name.each(|name| {
|
||||||
dom_operations::remove_attribute(element, &name).unwrap_throw();
|
bindings::remove_attribute(element, &intern(name));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -662,20 +691,22 @@ impl<A> DomBuilder<A> where A: AsRef<Element> {
|
||||||
D: OptionStr<Output = C>,
|
D: OptionStr<Output = C>,
|
||||||
E: Signal<Item = D> + 'static {
|
E: Signal<Item = D> + 'static {
|
||||||
|
|
||||||
let namespace = namespace.to_owned();
|
let namespace = intern(namespace);
|
||||||
|
|
||||||
set_option(self.element.as_ref().clone(), &mut self.callbacks, value, move |element, value| {
|
set_option(self.element.as_ref().clone(), &mut self.callbacks, value, move |element, value| {
|
||||||
match value {
|
match value {
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
let value = value.as_str();
|
let value = value.as_str();
|
||||||
|
// TODO should this intern ?
|
||||||
|
let value = intern(value);
|
||||||
|
|
||||||
name.each(|name| {
|
name.each(|name| {
|
||||||
dom_operations::set_attribute_ns(element, &namespace, &name, value).unwrap_throw();
|
bindings::set_attribute_ns(element, &namespace, &intern(name), &value);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
name.each(|name| {
|
name.each(|name| {
|
||||||
dom_operations::remove_attribute_ns(element, &namespace, &name).unwrap_throw();
|
bindings::remove_attribute_ns(element, &namespace, &intern(name));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -698,7 +729,7 @@ impl<A> DomBuilder<A> where A: AsRef<Element> {
|
||||||
where B: MultiStr + 'static,
|
where B: MultiStr + 'static,
|
||||||
C: Signal<Item = bool> + 'static {
|
C: Signal<Item = bool> + 'static {
|
||||||
|
|
||||||
let list = self.element.as_ref().class_list();
|
let element = self.element.as_ref().clone();
|
||||||
|
|
||||||
let mut is_set = false;
|
let mut is_set = false;
|
||||||
|
|
||||||
|
@ -708,7 +739,7 @@ impl<A> DomBuilder<A> where A: AsRef<Element> {
|
||||||
is_set = true;
|
is_set = true;
|
||||||
|
|
||||||
name.each(|name| {
|
name.each(|name| {
|
||||||
dom_operations::add_class(&list, name).unwrap_throw();
|
bindings::add_class(&element, &intern(name));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -717,7 +748,7 @@ impl<A> DomBuilder<A> where A: AsRef<Element> {
|
||||||
is_set = false;
|
is_set = false;
|
||||||
|
|
||||||
name.each(|name| {
|
name.each(|name| {
|
||||||
dom_operations::remove_class(&list, name).unwrap_throw();
|
bindings::remove_class(&element, &intern(name));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -759,12 +790,14 @@ impl<A> DomBuilder<A> where A: AsRef<Element> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn scroll_left_signal<B>(mut self, signal: B) -> Self where B: Signal<Item = Option<i32>> + 'static {
|
pub fn scroll_left_signal<B>(mut self, signal: B) -> Self where B: Signal<Item = Option<i32>> + 'static {
|
||||||
|
// TODO bindings function for this ?
|
||||||
self.set_scroll_signal(signal, Element::set_scroll_left);
|
self.set_scroll_signal(signal, Element::set_scroll_left);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn scroll_top_signal<B>(mut self, signal: B) -> Self where B: Signal<Item = Option<i32>> + 'static {
|
pub fn scroll_top_signal<B>(mut self, signal: B) -> Self where B: Signal<Item = Option<i32>> + 'static {
|
||||||
|
// TODO bindings function for this ?
|
||||||
self.set_scroll_signal(signal, Element::set_scroll_top);
|
self.set_scroll_signal(signal, Element::set_scroll_top);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -775,7 +808,7 @@ impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
|
||||||
pub fn style<B, C>(self, name: B, value: C) -> Self
|
pub fn style<B, C>(self, name: B, value: C) -> Self
|
||||||
where B: MultiStr,
|
where B: MultiStr,
|
||||||
C: MultiStr {
|
C: MultiStr {
|
||||||
set_style(&self.element.as_ref().style(), &name, value, false);
|
set_style(self.element.as_ref(), &name, value, false, true);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -783,7 +816,7 @@ impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
|
||||||
pub fn style_important<B, C>(self, name: B, value: C) -> Self
|
pub fn style_important<B, C>(self, name: B, value: C) -> Self
|
||||||
where B: MultiStr,
|
where B: MultiStr,
|
||||||
C: MultiStr {
|
C: MultiStr {
|
||||||
set_style(&self.element.as_ref().style(), &name, value, true);
|
set_style(self.element.as_ref(), &name, value, true, true);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -796,7 +829,7 @@ impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
|
||||||
D: OptionStr<Output = C>,
|
D: OptionStr<Output = C>,
|
||||||
E: Signal<Item = D> + 'static {
|
E: Signal<Item = D> + 'static {
|
||||||
|
|
||||||
set_style_signal(self.element.as_ref().style(), &mut self.callbacks, name, value, false);
|
set_style_signal(self.element.as_ref(), &mut self.callbacks, name, value, false);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -807,7 +840,7 @@ impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
|
||||||
D: OptionStr<Output = C>,
|
D: OptionStr<Output = C>,
|
||||||
E: Signal<Item = D> + 'static {
|
E: Signal<Item = D> + 'static {
|
||||||
|
|
||||||
set_style_signal(self.element.as_ref().style(), &mut self.callbacks, name, value, true);
|
set_style_signal(self.element.as_ref(), &mut self.callbacks, name, value, true);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -820,7 +853,12 @@ impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
|
||||||
// This needs to use `after_insert` because calling `.focus()` on an element before it is in the DOM has no effect
|
// This needs to use `after_insert` because calling `.focus()` on an element before it is in the DOM has no effect
|
||||||
self.callbacks.after_insert(move |_| {
|
self.callbacks.after_insert(move |_| {
|
||||||
// TODO avoid updating if the focused state hasn't changed ?
|
// TODO avoid updating if the focused state hasn't changed ?
|
||||||
dom_operations::set_focused(&element, value).unwrap_throw();
|
if value {
|
||||||
|
bindings::focus(&element);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
bindings::blur(&element);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
self
|
self
|
||||||
|
@ -837,7 +875,12 @@ impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
|
||||||
// TODO verify that this is correct under all circumstances
|
// TODO verify that this is correct under all circumstances
|
||||||
callbacks.after_remove(for_each(value, move |value| {
|
callbacks.after_remove(for_each(value, move |value| {
|
||||||
// TODO avoid updating if the focused state hasn't changed ?
|
// TODO avoid updating if the focused state hasn't changed ?
|
||||||
dom_operations::set_focused(&element, value).unwrap_throw();
|
if value {
|
||||||
|
bindings::focus(&element);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
bindings::blur(&element);
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -855,7 +898,7 @@ impl<A> DomBuilder<A> where A: AsRef<HtmlElement> {
|
||||||
// TODO better warning message for must_use
|
// TODO better warning message for must_use
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct StylesheetBuilder {
|
pub struct StylesheetBuilder {
|
||||||
element: CssStyleDeclaration,
|
element: CssStyleRule,
|
||||||
callbacks: Callbacks,
|
callbacks: Callbacks,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -866,30 +909,14 @@ impl StylesheetBuilder {
|
||||||
// TODO can this be made faster ?
|
// TODO can this be made faster ?
|
||||||
// TODO somehow share this safely between threads ?
|
// TODO somehow share this safely between threads ?
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static STYLESHEET: CssStyleSheet = {
|
static STYLESHEET: CssStyleSheet = bindings::create_stylesheet();
|
||||||
// TODO use createElementNS ?
|
|
||||||
let e = document().create_element("style").unwrap_throw();
|
|
||||||
// TODO maybe don't use unchecked ?
|
|
||||||
let e: &HtmlStyleElement = e.unchecked_ref();
|
|
||||||
|
|
||||||
e.set_type("text/css");
|
|
||||||
|
|
||||||
document().head().unwrap_throw().append_child(e).unwrap_throw();
|
|
||||||
|
|
||||||
// TODO maybe don't use unchecked ?
|
|
||||||
e.sheet().unwrap_throw().unchecked_into()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let element = STYLESHEET.with(|stylesheet| {
|
// TODO maybe intern ?
|
||||||
let rules = stylesheet.css_rules().unwrap_throw();
|
let selector = JsString::from(selector);
|
||||||
|
|
||||||
let length = rules.length();
|
let element = STYLESHEET.with(move |stylesheet| {
|
||||||
|
bindings::make_style_rule(stylesheet, &selector)
|
||||||
stylesheet.insert_rule_with_index(&format!("{}{{}}", selector), length).unwrap_throw();
|
|
||||||
|
|
||||||
// TODO maybe don't use unchecked ?
|
|
||||||
rules.get(length).unwrap_throw().unchecked_ref::<CssStyleRule>().style()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -902,7 +929,7 @@ impl StylesheetBuilder {
|
||||||
pub fn style<B, C>(self, name: B, value: C) -> Self
|
pub fn style<B, C>(self, name: B, value: C) -> Self
|
||||||
where B: MultiStr,
|
where B: MultiStr,
|
||||||
C: MultiStr {
|
C: MultiStr {
|
||||||
set_style(&self.element, &name, value, false);
|
set_style(&self.element, &name, value, false, true);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -910,7 +937,7 @@ impl StylesheetBuilder {
|
||||||
pub fn style_important<B, C>(self, name: B, value: C) -> Self
|
pub fn style_important<B, C>(self, name: B, value: C) -> Self
|
||||||
where B: MultiStr,
|
where B: MultiStr,
|
||||||
C: MultiStr {
|
C: MultiStr {
|
||||||
set_style(&self.element, &name, value, true);
|
set_style(&self.element, &name, value, true, true);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -921,7 +948,7 @@ impl StylesheetBuilder {
|
||||||
D: OptionStr<Output = C>,
|
D: OptionStr<Output = C>,
|
||||||
E: Signal<Item = D> + 'static {
|
E: Signal<Item = D> + 'static {
|
||||||
|
|
||||||
set_style_signal(self.element.clone(), &mut self.callbacks, name, value, false);
|
set_style_signal(&self.element, &mut self.callbacks, name, value, false);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -932,7 +959,7 @@ impl StylesheetBuilder {
|
||||||
D: OptionStr<Output = C>,
|
D: OptionStr<Output = C>,
|
||||||
E: Signal<Item = D> + 'static {
|
E: Signal<Item = D> + 'static {
|
||||||
|
|
||||||
set_style_signal(self.element.clone(), &mut self.callbacks, name, value, true);
|
set_style_signal(&self.element, &mut self.callbacks, name, value, true);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
use wasm_bindgen::{JsValue, UnwrapThrowExt};
|
|
||||||
use web_sys::{Node, HtmlElement, Element, DomTokenList};
|
|
||||||
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn get_at(parent: &Node, index: u32) -> Node {
|
|
||||||
parent.child_nodes().get(index).unwrap_throw()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO make this more efficient
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn move_from_to(parent: &Node, old_index: u32, new_index: u32) -> Result<(), JsValue> {
|
|
||||||
let child = get_at(parent, old_index);
|
|
||||||
|
|
||||||
parent.remove_child(&child)?;
|
|
||||||
|
|
||||||
insert_at(parent, new_index, &child)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO make this more efficient
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn insert_at(parent: &Node, index: u32, child: &Node) -> Result<(), JsValue> {
|
|
||||||
parent.insert_before(child, Some(&get_at(parent, index)))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO make this more efficient
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn update_at(parent: &Node, index: u32, child: &Node) -> Result<(), JsValue> {
|
|
||||||
parent.replace_child(child, &get_at(parent, index))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO make this more efficient
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn remove_at(parent: &Node, index: u32) -> Result<(), JsValue> {
|
|
||||||
parent.remove_child(&get_at(parent, index))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn set_focused(element: &HtmlElement, focused: bool) -> Result<(), JsValue> {
|
|
||||||
if focused {
|
|
||||||
element.focus()?;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
element.blur()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn add_class(list: &DomTokenList, name: &str) -> Result<(), JsValue> {
|
|
||||||
list.add_1(name)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn remove_class(list: &DomTokenList, name: &str) -> Result<(), JsValue> {
|
|
||||||
list.remove_1(name)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO check that the attribute *actually* was changed
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn set_attribute(element: &Element, name: &str, value: &str) -> Result<(), JsValue> {
|
|
||||||
element.set_attribute(name, value)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO check that the attribute *actually* was changed
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn set_attribute_ns(element: &Element, namespace: &str, name: &str, value: &str) -> Result<(), JsValue> {
|
|
||||||
element.set_attribute_ns(Some(namespace), name, value)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn remove_attribute_ns(element: &Element, namespace: &str, name: &str) -> Result<(), JsValue> {
|
|
||||||
element.remove_attribute_ns(Some(namespace), name)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn remove_attribute(element: &Element, name: &str) -> Result<(), JsValue> {
|
|
||||||
element.remove_attribute(name)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn remove_all_children(node: &Node) {
|
|
||||||
node.set_text_content(None);
|
|
||||||
}
|
|
|
@ -82,6 +82,7 @@ make_event!(Input, "input" => web_sys::InputEvent);
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
// TODO should this work on other types as well ?
|
// TODO should this work on other types as well ?
|
||||||
|
// TODO return JsString ?
|
||||||
pub fn value(&self) -> Option<String> {
|
pub fn value(&self) -> Option<String> {
|
||||||
let target = self.target()?;
|
let target = self.target()?;
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,14 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
mod cache;
|
||||||
|
mod bindings;
|
||||||
mod callbacks;
|
mod callbacks;
|
||||||
mod operations;
|
mod operations;
|
||||||
mod dom_operations;
|
|
||||||
mod dom;
|
mod dom;
|
||||||
|
|
||||||
pub use dom::*;
|
pub use dom::*;
|
||||||
|
pub use cache::*;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
pub mod animation;
|
pub mod animation;
|
||||||
pub mod routing;
|
pub mod routing;
|
||||||
|
|
|
@ -8,13 +8,12 @@ use futures_signals::{cancelable_future, CancelableFutureHandle};
|
||||||
use futures_signals::signal::{Signal, SignalExt};
|
use futures_signals::signal::{Signal, SignalExt};
|
||||||
use futures_signals::signal_vec::{VecDiff, SignalVec, SignalVecExt};
|
use futures_signals::signal_vec::{VecDiff, SignalVec, SignalVecExt};
|
||||||
use web_sys::Node;
|
use web_sys::Node;
|
||||||
use wasm_bindgen::{JsValue, UnwrapThrowExt};
|
use wasm_bindgen::UnwrapThrowExt;
|
||||||
use wasm_bindgen_futures::futures_0_3::spawn_local;
|
use wasm_bindgen_futures::futures_0_3::spawn_local;
|
||||||
|
|
||||||
use crate::dom_operations;
|
use crate::bindings;
|
||||||
use crate::dom::Dom;
|
use crate::dom::Dom;
|
||||||
use crate::callbacks::Callbacks;
|
use crate::callbacks::Callbacks;
|
||||||
use crate::utils::document;
|
|
||||||
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -53,45 +52,15 @@ fn for_each_vec<A, B>(signal: A, mut callback: B) -> CancelableFutureHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
// TODO inline this ?
|
|
||||||
pub fn insert_children_signal<A, B, C>(element: &A, callbacks: &mut Callbacks, signal: C)
|
|
||||||
where A: INode + Clone + 'static,
|
|
||||||
B: IntoIterator<Item = Dom>,
|
|
||||||
C: Signal<Item = B> + 'static {
|
|
||||||
|
|
||||||
let element = element.clone();
|
|
||||||
|
|
||||||
let mut old_children: Vec<Dom> = vec![];
|
|
||||||
|
|
||||||
let handle = for_each(signal, move |value| {
|
|
||||||
dom_operations::remove_all_children(&element);
|
|
||||||
|
|
||||||
old_children = value.into_iter().map(|mut dom| {
|
|
||||||
element.append_child(&dom.element);
|
|
||||||
|
|
||||||
// TODO don't trigger this if the parent isn't inserted into the DOM
|
|
||||||
dom.callbacks.trigger_after_insert();
|
|
||||||
|
|
||||||
dom
|
|
||||||
}).collect();
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO verify that this will drop `old_children`
|
|
||||||
callbacks.after_remove(handle);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn insert_children_iter<'a, A: IntoIterator<Item = &'a mut Dom>>(element: &Node, callbacks: &mut Callbacks, value: A) -> Result<(), JsValue> {
|
pub(crate) fn insert_children_iter<'a, A: IntoIterator<Item = &'a mut Dom>>(element: &Node, callbacks: &mut Callbacks, value: A) {
|
||||||
for dom in value.into_iter() {
|
for dom in value.into_iter() {
|
||||||
// TODO can this be made more efficient ?
|
// TODO can this be made more efficient ?
|
||||||
callbacks.after_insert.append(&mut dom.callbacks.after_insert);
|
callbacks.after_insert.append(&mut dom.callbacks.after_insert);
|
||||||
callbacks.after_remove.append(&mut dom.callbacks.after_remove);
|
callbacks.after_remove.append(&mut dom.callbacks.after_remove);
|
||||||
|
|
||||||
element.append_child(&dom.element)?;
|
bindings::append_child(element, &dom.element);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -126,12 +95,12 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_change(state: &mut State, element: &Node, change: VecDiff<Dom>) -> Result<(), JsValue> {
|
fn process_change(state: &mut State, element: &Node, change: VecDiff<Dom>) {
|
||||||
match change {
|
match change {
|
||||||
VecDiff::Replace { values } => {
|
VecDiff::Replace { values } => {
|
||||||
// TODO is this correct ?
|
// TODO is this correct ?
|
||||||
if state.children.len() > 0 {
|
if state.children.len() > 0 {
|
||||||
dom_operations::remove_all_children(element);
|
bindings::remove_all_children(element);
|
||||||
|
|
||||||
for dom in state.children.drain(..) {
|
for dom in state.children.drain(..) {
|
||||||
dom.callbacks.discard();
|
dom.callbacks.discard();
|
||||||
|
@ -142,25 +111,12 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
|
||||||
|
|
||||||
let is_inserted = state.is_inserted;
|
let is_inserted = state.is_inserted;
|
||||||
|
|
||||||
let mut has_inserts = false;
|
|
||||||
|
|
||||||
// TODO is it worth it to use fragments ?
|
|
||||||
let fragment = document().create_document_fragment();
|
|
||||||
|
|
||||||
for dom in state.children.iter_mut() {
|
for dom in state.children.iter_mut() {
|
||||||
dom.callbacks.leak();
|
dom.callbacks.leak();
|
||||||
|
|
||||||
fragment.append_child(&dom.element)?;
|
bindings::append_child(element, &dom.element);
|
||||||
|
|
||||||
if !dom.callbacks.after_insert.is_empty() {
|
if is_inserted {
|
||||||
has_inserts = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
element.append_child(&fragment)?;
|
|
||||||
|
|
||||||
if is_inserted && has_inserts {
|
|
||||||
for dom in state.children.iter_mut() {
|
|
||||||
dom.callbacks.trigger_after_insert();
|
dom.callbacks.trigger_after_insert();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,7 +124,7 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
|
||||||
|
|
||||||
VecDiff::InsertAt { index, mut value } => {
|
VecDiff::InsertAt { index, mut value } => {
|
||||||
// TODO better usize -> u32 conversion
|
// TODO better usize -> u32 conversion
|
||||||
dom_operations::insert_at(element, index as u32, &value.element)?;
|
bindings::insert_at(element, index as u32, &value.element);
|
||||||
|
|
||||||
value.callbacks.leak();
|
value.callbacks.leak();
|
||||||
|
|
||||||
|
@ -181,7 +137,7 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
|
||||||
},
|
},
|
||||||
|
|
||||||
VecDiff::Push { mut value } => {
|
VecDiff::Push { mut value } => {
|
||||||
element.append_child(&value.element)?;
|
bindings::append_child(element, &value.element);
|
||||||
|
|
||||||
value.callbacks.leak();
|
value.callbacks.leak();
|
||||||
|
|
||||||
|
@ -195,7 +151,7 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
|
||||||
|
|
||||||
VecDiff::UpdateAt { index, mut value } => {
|
VecDiff::UpdateAt { index, mut value } => {
|
||||||
// TODO better usize -> u32 conversion
|
// TODO better usize -> u32 conversion
|
||||||
dom_operations::update_at(element, index as u32, &value.element)?;
|
bindings::update_at(element, index as u32, &value.element);
|
||||||
|
|
||||||
value.callbacks.leak();
|
value.callbacks.leak();
|
||||||
|
|
||||||
|
@ -213,15 +169,16 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
|
||||||
VecDiff::Move { old_index, new_index } => {
|
VecDiff::Move { old_index, new_index } => {
|
||||||
let value = state.children.remove(old_index);
|
let value = state.children.remove(old_index);
|
||||||
|
|
||||||
state.children.insert(new_index, value);
|
bindings::remove_child(element, &value.element);
|
||||||
|
|
||||||
// TODO better usize -> u32 conversion
|
// TODO better usize -> u32 conversion
|
||||||
dom_operations::move_from_to(element, old_index as u32, new_index as u32)?;
|
bindings::insert_at(element, new_index as u32, &value.element);
|
||||||
|
|
||||||
|
state.children.insert(new_index, value);
|
||||||
},
|
},
|
||||||
|
|
||||||
VecDiff::RemoveAt { index } => {
|
VecDiff::RemoveAt { index } => {
|
||||||
// TODO better usize -> u32 conversion
|
// TODO better usize -> u32 conversion
|
||||||
dom_operations::remove_at(element, index as u32)?;
|
bindings::remove_at(element, index as u32);
|
||||||
|
|
||||||
state.children.remove(index).callbacks.discard();
|
state.children.remove(index).callbacks.discard();
|
||||||
},
|
},
|
||||||
|
@ -231,7 +188,7 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
|
||||||
|
|
||||||
// TODO create remove_last_child function ?
|
// TODO create remove_last_child function ?
|
||||||
// TODO better usize -> u32 conversion
|
// TODO better usize -> u32 conversion
|
||||||
dom_operations::remove_at(element, index as u32)?;
|
bindings::remove_at(element, index as u32);
|
||||||
|
|
||||||
state.children.pop().unwrap_throw().callbacks.discard();
|
state.children.pop().unwrap_throw().callbacks.discard();
|
||||||
},
|
},
|
||||||
|
@ -240,7 +197,7 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
|
||||||
// TODO is this correct ?
|
// TODO is this correct ?
|
||||||
// TODO is this needed, or is it guaranteed by VecDiff ?
|
// TODO is this needed, or is it guaranteed by VecDiff ?
|
||||||
if state.children.len() > 0 {
|
if state.children.len() > 0 {
|
||||||
dom_operations::remove_all_children(element);
|
bindings::remove_all_children(element);
|
||||||
|
|
||||||
for dom in state.children.drain(..) {
|
for dom in state.children.drain(..) {
|
||||||
dom.callbacks.discard();
|
dom.callbacks.discard();
|
||||||
|
@ -248,14 +205,12 @@ pub(crate) fn insert_children_signal_vec<A>(element: Node, callbacks: &mut Callb
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO verify that this will drop `children`
|
// TODO verify that this will drop `children`
|
||||||
callbacks.after_remove(for_each_vec(signal, move |change| {
|
callbacks.after_remove(for_each_vec(signal, move |change| {
|
||||||
let mut state = state.lock().unwrap_throw();
|
let mut state = state.lock().unwrap_throw();
|
||||||
|
|
||||||
process_change(&mut state, &element, change).unwrap_throw();
|
process_change(&mut state, &element, change);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
127
src/routing.rs
127
src/routing.rs
|
@ -1,128 +1,81 @@
|
||||||
use wasm_bindgen::{JsValue, UnwrapThrowExt};
|
use std::borrow::Cow;
|
||||||
use web_sys::{window, Url, EventTarget, HtmlElement};
|
|
||||||
use futures_signals::signal::{Mutable, Signal};
|
use js_sys::JsString;
|
||||||
|
use web_sys::{EventTarget, HtmlElement};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use futures_signals::signal::{Mutable, ReadOnlyMutable};
|
||||||
use gloo::events::EventListener;
|
use gloo::events::EventListener;
|
||||||
|
|
||||||
|
use crate::bindings;
|
||||||
use crate::dom::{Dom, DomBuilder};
|
use crate::dom::{Dom, DomBuilder};
|
||||||
use crate::events;
|
use crate::events;
|
||||||
|
|
||||||
|
|
||||||
/*pub struct State<A> {
|
|
||||||
value: Mutable<Option<A>>,
|
|
||||||
callback: Value,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A> State<A> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
// TODO replace with stdweb function
|
|
||||||
let value = Mutable::new(js!( return history.state; ).try_into().unwrap_throw());
|
|
||||||
|
|
||||||
let callback = |state: Option<A>| {
|
|
||||||
value.set(state);
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
value,
|
|
||||||
callback: js!(
|
|
||||||
var callback = @{callback};
|
|
||||||
|
|
||||||
addEventListener("popstate", function (e) {
|
|
||||||
callback(e.state);
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
return callback;
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(&self, value: A) {
|
|
||||||
window().history().replace_state(value, "", None).unwrap_throw();
|
|
||||||
self.value.set(value);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
fn current_url_string() -> Result<String, JsValue> {
|
|
||||||
Ok(window().unwrap_throw().location().href()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO inline ?
|
// TODO inline ?
|
||||||
fn change_url(mutable: &Mutable<Url>) -> Result<(), JsValue> {
|
fn change_url(mutable: &Mutable<String>) {
|
||||||
let mut lock = mutable.lock_mut();
|
let mut lock = mutable.lock_mut();
|
||||||
|
|
||||||
let new_url = current_url_string()?;
|
let new_url = String::from(bindings::current_url());
|
||||||
|
|
||||||
// TODO test that this doesn't notify if the URLs are the same
|
|
||||||
// TODO helper method for this
|
// TODO helper method for this
|
||||||
// TODO can this be made more efficient ?
|
// TODO can this be made more efficient ?
|
||||||
if lock.href() != new_url {
|
if *lock != new_url {
|
||||||
*lock = Url::new(&new_url)?;
|
*lock = new_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct CurrentUrl {
|
struct CurrentUrl {
|
||||||
value: Mutable<Url>,
|
value: Mutable<String>,
|
||||||
_listener: EventListener,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CurrentUrl {
|
impl CurrentUrl {
|
||||||
fn new() -> Result<Self, JsValue> {
|
fn new() -> Self {
|
||||||
// TODO can this be made more efficient ?
|
// TODO can this be made more efficient ?
|
||||||
let value = Mutable::new(Url::new(¤t_url_string()?)?);
|
let value = Mutable::new(String::from(bindings::current_url()));
|
||||||
|
|
||||||
Ok(Self {
|
// TODO clean this up somehow ?
|
||||||
_listener: EventListener::new(&window().unwrap_throw(), "popstate", {
|
EventListener::new(&bindings::window(), "popstate", {
|
||||||
let value = value.clone();
|
let value = value.clone();
|
||||||
move |_| {
|
move |_| {
|
||||||
change_url(&value).unwrap_throw();
|
change_url(&value);
|
||||||
}
|
}
|
||||||
}),
|
}).forget();
|
||||||
|
|
||||||
|
Self {
|
||||||
value,
|
value,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO somehow share this safely between threads ?
|
|
||||||
thread_local! {
|
lazy_static! {
|
||||||
static URL: CurrentUrl = CurrentUrl::new().unwrap_throw();
|
static ref URL: CurrentUrl = CurrentUrl::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn current_url() -> Url {
|
pub fn url() -> ReadOnlyMutable<String> {
|
||||||
URL.with(|url| url.value.get_cloned())
|
URL.value.read_only()
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn url() -> impl Signal<Item = Url> {
|
|
||||||
URL.with(|url| url.value.signal_cloned())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO if URL hasn't been created yet, don't create it
|
// TODO if URL hasn't been created yet, don't create it
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn go_to_url(new_url: &str) {
|
pub fn go_to_url(new_url: &str) {
|
||||||
window()
|
// TODO intern ?
|
||||||
.unwrap_throw()
|
bindings::go_to_url(&JsString::from(new_url));
|
||||||
.history()
|
|
||||||
.unwrap_throw()
|
|
||||||
// TODO is this the best state object to use ?
|
|
||||||
.push_state_with_url(&JsValue::NULL, "", Some(new_url))
|
|
||||||
.unwrap_throw();
|
|
||||||
|
|
||||||
URL.with(|url| {
|
change_url(&URL.value);
|
||||||
change_url(&url.value).unwrap_throw();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO somehow use &str rather than String, maybe Cow ?
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn on_click_go_to_url<A>(new_url: String) -> impl FnOnce(DomBuilder<A>) -> DomBuilder<A> where A: AsRef<EventTarget> {
|
pub fn on_click_go_to_url<A, B>(new_url: A) -> impl FnOnce(DomBuilder<B>) -> DomBuilder<B>
|
||||||
|
where A: Into<Cow<'static, str>>,
|
||||||
|
B: AsRef<EventTarget> {
|
||||||
|
let new_url = new_url.into();
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
move |dom| {
|
move |dom| {
|
||||||
dom.event_preventable(move |e: events::Click| {
|
dom.event_preventable(move |e: events::Click| {
|
||||||
|
@ -134,12 +87,16 @@ pub fn on_click_go_to_url<A>(new_url: String) -> impl FnOnce(DomBuilder<A>) -> D
|
||||||
|
|
||||||
|
|
||||||
// TODO better type than HtmlElement
|
// TODO better type than HtmlElement
|
||||||
|
// TODO maybe make this a macro ?
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn link<F>(url: &str, f: F) -> Dom where F: FnOnce(DomBuilder<HtmlElement>) -> DomBuilder<HtmlElement> {
|
pub fn link<A, F>(url: A, f: F) -> Dom
|
||||||
|
where A: Into<Cow<'static, str>>,
|
||||||
|
F: FnOnce(DomBuilder<HtmlElement>) -> DomBuilder<HtmlElement> {
|
||||||
|
let url = url.into();
|
||||||
|
|
||||||
html!("a", {
|
html!("a", {
|
||||||
.attribute("href", url)
|
.attribute("href", &url)
|
||||||
// TODO somehow avoid this allocation
|
.apply(on_click_go_to_url(url))
|
||||||
.apply(on_click_go_to_url(url.to_string()))
|
|
||||||
.apply(f)
|
.apply(f)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
10
src/utils.rs
10
src/utils.rs
|
@ -1,8 +1,9 @@
|
||||||
use std::mem::ManuallyDrop;
|
use std::mem::ManuallyDrop;
|
||||||
|
|
||||||
use discard::Discard;
|
use discard::Discard;
|
||||||
use wasm_bindgen::UnwrapThrowExt;
|
use web_sys::{EventTarget};
|
||||||
use web_sys::{window, Document, EventTarget};
|
|
||||||
use gloo::events::{EventListener, EventListenerOptions};
|
use gloo::events::{EventListener, EventListenerOptions};
|
||||||
|
|
||||||
use crate::traits::StaticEvent;
|
use crate::traits::StaticEvent;
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,11 +24,6 @@ pub(crate) fn on_with_options<E, F>(element: &EventTarget, options: EventListene
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub(crate) fn document() -> Document {
|
|
||||||
window().unwrap_throw().document().unwrap_throw()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO move this into the discard crate
|
// TODO move this into the discard crate
|
||||||
// TODO verify that this is correct and doesn't leak memory or cause memory safety
|
// TODO verify that this is correct and doesn't leak memory or cause memory safety
|
||||||
pub(crate) struct ValueDiscard<A>(ManuallyDrop<A>);
|
pub(crate) struct ValueDiscard<A>(ManuallyDrop<A>);
|
||||||
|
|
Loading…
Reference in New Issue