diff --git a/Cargo.toml b/Cargo.toml index 54d140c9d64..1dd60ed2ad0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,8 +22,14 @@ travis-ci = { repository = "yewstack/yew" } anyhow = "1" anymap = "0.12" bincode = { version = "~1.2.1", optional = true } +cfg-if = "0.1" +cfg-match = "0.2" +console_error_panic_hook = { version = "0.1", optional = true } +futures = { version = "0.3", optional = true } +gloo = { version = "0.2", optional = true } http = "0.2" indexmap = "1.0.2" +js-sys = { version = "0.3", optional = true } log = "0.4" proc-macro-hack = "0.5" proc-macro-nested = "0.1" @@ -33,14 +39,73 @@ serde_cbor = { version = "0.11.1", optional = true } serde_json = "1.0" serde_yaml = { version = "0.8.3", optional = true } slab = "0.4" -stdweb = "0.4.20" +stdweb = { version = "0.4.20", optional = true } thiserror = "1" toml = { version = "0.5", optional = true } +wasm-bindgen = { version = "0.2.58", optional = true } +wasm-bindgen-futures = { version = "0.4", optional = true } yew-macro = { version = "0.12.0", path = "crates/macro" } +[dependencies.web-sys] +version = "0.3" +optional = true +features = [ + "AbortController", + "AbortSignal", + "BinaryType", + "Blob", + "BlobPropertyBag", + "console", + "DedicatedWorkerGlobalScope", + "Document", + "DomException", + "DomTokenList", + "DragEvent", + "Element", + "Event", + "EventTarget", + "File", + "FileList", + "FileReader", + "FocusEvent", + "Headers", + "HtmlElement", + "HtmlInputElement", + "HtmlSelectElement", + "HtmlTextAreaElement", + "KeyboardEvent", + "Location", + "MessageEvent", + "MouseEvent", + "Node", + "ObserverCallback", + "PointerEvent", + "ReferrerPolicy", + "Request", + "RequestCache", + "RequestCredentials", + "RequestInit", + "RequestMode", + "RequestRedirect", + "Response", + "Storage", + "Text", + "TouchEvent", + "UiEvent", + "Url", + "WebSocket", + "WheelEvent", + "Window", + "Worker", + "WorkerGlobalScope", + "WorkerOptions", +] + +# Changes here must be reflected in `build.rs` [target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies] wasm-bindgen = "0.2.58" +# Changes here must be reflected in `build.rs` [target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dev-dependencies] wasm-bindgen-test = "0.3.4" base64 = "0.11.0" @@ -57,9 +122,10 @@ rmp-serde = "0.14.0" bincode = "~1.2.1" [features] -default = ["services", "agent"] +default = [] +std_web = ["stdweb"] +web_sys = ["console_error_panic_hook", "futures", "gloo", "js-sys", "web-sys", "wasm-bindgen", "wasm-bindgen-futures"] doc_test = [] -web_test = [] wasm_test = [] services = [] agent = ["bincode"] diff --git a/build.rs b/build.rs index a0445febb0e..6376e9a727b 100644 --- a/build.rs +++ b/build.rs @@ -1,9 +1,28 @@ use std::env; pub fn main() { - let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); + if cfg!(all(feature = "web_sys", feature = "std_web")) { + panic!("Yew does not allow the `web_sys` and `std_web` cargo features to be used simultaneously"); + } else if cfg!(not(any(feature = "web_sys", feature = "std_web"))) { + panic!("Yew requires selecting either the `web_sys` or `std_web` cargo feature"); + } + + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + let using_wasi = target_os == "wasi"; + let cargo_web = env::var("COMPILING_UNDER_CARGO_WEB").unwrap_or_default(); - if target_arch == "wasm32" && cargo_web != "1" { - println!("cargo:rustc-cfg=feature=\"wasm_bindgen_test\""); + let using_cargo_web = cargo_web == "1"; + if using_cargo_web && cfg!(feature = "web_sys") { + panic!("cargo-web is not compatible with web-sys"); + } + + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); + let using_wasm_bindgen = target_arch == "wasm32" && !using_cargo_web && !using_wasi; + if !using_wasm_bindgen && cfg!(all(feature = "web_sys", not(feature = "doc_test"))) { + let target = env::var("TARGET").unwrap_or_default(); + panic!( + "Selected target `{}` is not compatible with web-sys", + target + ); } } diff --git a/ci/check_examples.sh b/ci/check_examples.sh index 7b46284a6c1..686788cdc3f 100755 --- a/ci/check_examples.sh +++ b/ci/check_examples.sh @@ -10,12 +10,12 @@ cd examples/showcase if [ "$emscripten_supported" == "0" ]; then # TODO - Emscripten builds are broken on rustc > 1.39.0 - cargo web build --target asmjs-unknown-emscripten - cargo web build --target wasm32-unknown-emscripten + cargo web build --target asmjs-unknown-emscripten --features std_web + cargo web build --target wasm32-unknown-emscripten --features std_web fi -# TODO showcase doesn't support wasm-bindgen yet -cargo web build --target wasm32-unknown-unknown +cargo web build --target wasm32-unknown-unknown --features std_web +cargo build --target wasm32-unknown-unknown --features web_sys # Reset cwd cd ../.. diff --git a/ci/run_checks.sh b/ci/run_checks.sh index 65e7ce91cd6..37ecafc7b47 100755 --- a/ci/run_checks.sh +++ b/ci/run_checks.sh @@ -8,4 +8,5 @@ fi set -euxo pipefail cargo fmt --all -- --check -cargo clippy -- --deny=warnings +cargo clippy --features std_web -- --deny=warnings +cargo clippy --target wasm32-unknown-unknown --features web_sys -- --deny=warnings diff --git a/ci/run_tests.sh b/ci/run_tests.sh index 6f80d409d2c..71b17adcdbd 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -5,12 +5,16 @@ set -euxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_ if [ "$emscripten_supported" == "0" ]; then # TODO - Emscripten builds are broken on rustc > 1.39.0 - cargo web test --features web_test --target asmjs-unknown-emscripten - cargo web test --features web_test --target wasm32-unknown-emscripten + cargo web test --target asmjs-unknown-emscripten --features std_web + cargo web test --target wasm32-unknown-emscripten --features std_web fi -cargo test --features wasm_test --target wasm32-unknown-unknown -cargo test --test macro_test -cargo test --test derive_props_test -cargo test --doc --all-features -(cd crates/macro && cargo test --doc) +cargo test --target wasm32-unknown-unknown --features wasm_test,std_web +cargo test --target wasm32-unknown-unknown --features wasm_test,web_sys +cargo test --doc --features doc_test,wasm_test,yaml,msgpack,cbor,std_web +cargo test --doc --features doc_test,wasm_test,yaml,msgpack,cbor,web_sys + +(cd crates/macro \ + && cargo test --test macro_test \ + && cargo test --test derive_props_test \ + && cargo test --doc) diff --git a/crates/macro/Cargo.toml b/crates/macro/Cargo.toml index 9cd8187605c..3594e9672ec 100644 --- a/crates/macro/Cargo.toml +++ b/crates/macro/Cargo.toml @@ -25,8 +25,11 @@ proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0", features = ["full", "extra-traits"] } +# testing [dev-dependencies] -yew = { path = "../.." } +rustversion = "1.0" +trybuild = "1.0" +yew = { path = "../..", features = ["std_web"] } [build-dependencies] diff --git a/tests/derive_props/fail.rs b/crates/macro/tests/derive_props/fail.rs similarity index 100% rename from tests/derive_props/fail.rs rename to crates/macro/tests/derive_props/fail.rs diff --git a/tests/derive_props/fail.stderr b/crates/macro/tests/derive_props/fail.stderr similarity index 93% rename from tests/derive_props/fail.stderr rename to crates/macro/tests/derive_props/fail.stderr index 66a942fc742..d303bca96bb 100644 --- a/tests/derive_props/fail.stderr +++ b/crates/macro/tests/derive_props/fail.stderr @@ -39,6 +39,10 @@ error[E0599]: no method named `build` found for type `t3::PropsBuilder` + | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `build`, perhaps you need to implement it: + candidate #1: `proc_macro::bridge::server::TokenStreamBuilder` error[E0599]: no method named `b` found for type `t4::PropsBuilder` in the current scope --> $DIR/fail.rs:48:26 diff --git a/tests/derive_props/pass.rs b/crates/macro/tests/derive_props/pass.rs similarity index 100% rename from tests/derive_props/pass.rs rename to crates/macro/tests/derive_props/pass.rs diff --git a/tests/derive_props_test.rs b/crates/macro/tests/derive_props_test.rs similarity index 68% rename from tests/derive_props_test.rs rename to crates/macro/tests/derive_props_test.rs index 016d8842bf7..8cc45a6a90e 100644 --- a/tests/derive_props_test.rs +++ b/crates/macro/tests/derive_props_test.rs @@ -1,5 +1,5 @@ #[allow(dead_code)] -#[rustversion::attr(stable(1.41), cfg_attr(not(feature = "web_test"), test))] +#[rustversion::attr(stable(1.41), test)] fn tests() { let t = trybuild::TestCases::new(); t.pass("tests/derive_props/pass.rs"); diff --git a/tests/macro/html-block-fail.rs b/crates/macro/tests/macro/html-block-fail.rs similarity index 100% rename from tests/macro/html-block-fail.rs rename to crates/macro/tests/macro/html-block-fail.rs diff --git a/tests/macro/html-block-fail.stderr b/crates/macro/tests/macro/html-block-fail.stderr similarity index 100% rename from tests/macro/html-block-fail.stderr rename to crates/macro/tests/macro/html-block-fail.stderr diff --git a/tests/macro/html-block-pass.rs b/crates/macro/tests/macro/html-block-pass.rs similarity index 100% rename from tests/macro/html-block-pass.rs rename to crates/macro/tests/macro/html-block-pass.rs diff --git a/tests/macro/html-component-fail-unimplemented.rs b/crates/macro/tests/macro/html-component-fail-unimplemented.rs similarity index 100% rename from tests/macro/html-component-fail-unimplemented.rs rename to crates/macro/tests/macro/html-component-fail-unimplemented.rs diff --git a/tests/macro/html-component-fail-unimplemented.stderr b/crates/macro/tests/macro/html-component-fail-unimplemented.stderr similarity index 100% rename from tests/macro/html-component-fail-unimplemented.stderr rename to crates/macro/tests/macro/html-component-fail-unimplemented.stderr diff --git a/tests/macro/html-component-fail.rs b/crates/macro/tests/macro/html-component-fail.rs similarity index 100% rename from tests/macro/html-component-fail.rs rename to crates/macro/tests/macro/html-component-fail.rs diff --git a/tests/macro/html-component-fail.stderr b/crates/macro/tests/macro/html-component-fail.stderr similarity index 94% rename from tests/macro/html-component-fail.stderr rename to crates/macro/tests/macro/html-component-fail.stderr index 67c88484427..3f792855b44 100644 --- a/tests/macro/html-component-fail.stderr +++ b/crates/macro/tests/macro/html-component-fail.stderr @@ -197,6 +197,10 @@ error[E0599]: no method named `string` found for type `ChildPropertiesBuilder }; | ^^^^^^ method not found in `ChildPropertiesBuilder` + | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `string`, perhaps you need to implement it: + candidate #1: `proc_macro::bridge::server::Literal` error[E0599]: no method named `children` found for type `ChildPropertiesBuilder` in the current scope --> $DIR/html-component-fail.rs:93:5 @@ -218,6 +222,9 @@ error[E0599]: no method named `build` found for type `ChildContainerPropertiesBu 95 | html! { }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ChildContainerPropertiesBuilder` | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `build`, perhaps you need to implement it: + candidate #1: `proc_macro::bridge::server::TokenStreamBuilder` = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error[E0599]: no method named `build` found for type `ChildContainerPropertiesBuilder` in the current scope @@ -229,6 +236,9 @@ error[E0599]: no method named `build` found for type `ChildContainerPropertiesBu 96 | html! { }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `ChildContainerPropertiesBuilder` | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `build`, perhaps you need to implement it: + candidate #1: `proc_macro::bridge::server::TokenStreamBuilder` = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error[E0277]: the trait bound `yew::virtual_dom::vcomp::VChild: std::convert::From<&str>` is not satisfied diff --git a/tests/macro/html-component-pass.rs b/crates/macro/tests/macro/html-component-pass.rs similarity index 100% rename from tests/macro/html-component-pass.rs rename to crates/macro/tests/macro/html-component-pass.rs diff --git a/tests/macro/html-iterable-fail.rs b/crates/macro/tests/macro/html-iterable-fail.rs similarity index 100% rename from tests/macro/html-iterable-fail.rs rename to crates/macro/tests/macro/html-iterable-fail.rs diff --git a/tests/macro/html-iterable-fail.stderr b/crates/macro/tests/macro/html-iterable-fail.stderr similarity index 100% rename from tests/macro/html-iterable-fail.stderr rename to crates/macro/tests/macro/html-iterable-fail.stderr diff --git a/tests/macro/html-iterable-pass.rs b/crates/macro/tests/macro/html-iterable-pass.rs similarity index 100% rename from tests/macro/html-iterable-pass.rs rename to crates/macro/tests/macro/html-iterable-pass.rs diff --git a/tests/macro/html-list-fail.rs b/crates/macro/tests/macro/html-list-fail.rs similarity index 100% rename from tests/macro/html-list-fail.rs rename to crates/macro/tests/macro/html-list-fail.rs diff --git a/tests/macro/html-list-fail.stderr b/crates/macro/tests/macro/html-list-fail.stderr similarity index 100% rename from tests/macro/html-list-fail.stderr rename to crates/macro/tests/macro/html-list-fail.stderr diff --git a/tests/macro/html-list-pass.rs b/crates/macro/tests/macro/html-list-pass.rs similarity index 100% rename from tests/macro/html-list-pass.rs rename to crates/macro/tests/macro/html-list-pass.rs diff --git a/tests/macro/html-node-fail.rs b/crates/macro/tests/macro/html-node-fail.rs similarity index 100% rename from tests/macro/html-node-fail.rs rename to crates/macro/tests/macro/html-node-fail.rs diff --git a/tests/macro/html-node-fail.stderr b/crates/macro/tests/macro/html-node-fail.stderr similarity index 100% rename from tests/macro/html-node-fail.stderr rename to crates/macro/tests/macro/html-node-fail.stderr diff --git a/tests/macro/html-node-pass.rs b/crates/macro/tests/macro/html-node-pass.rs similarity index 100% rename from tests/macro/html-node-pass.rs rename to crates/macro/tests/macro/html-node-pass.rs diff --git a/tests/macro/html-tag-fail.rs b/crates/macro/tests/macro/html-tag-fail.rs similarity index 100% rename from tests/macro/html-tag-fail.rs rename to crates/macro/tests/macro/html-tag-fail.rs diff --git a/tests/macro/html-tag-fail.stderr b/crates/macro/tests/macro/html-tag-fail.stderr similarity index 100% rename from tests/macro/html-tag-fail.stderr rename to crates/macro/tests/macro/html-tag-fail.stderr diff --git a/tests/macro/html-tag-pass.rs b/crates/macro/tests/macro/html-tag-pass.rs similarity index 100% rename from tests/macro/html-tag-pass.rs rename to crates/macro/tests/macro/html-tag-pass.rs diff --git a/tests/macro/test_component.rs b/crates/macro/tests/macro/test_component.rs similarity index 100% rename from tests/macro/test_component.rs rename to crates/macro/tests/macro/test_component.rs diff --git a/tests/macro_test.rs b/crates/macro/tests/macro_test.rs similarity index 90% rename from tests/macro_test.rs rename to crates/macro/tests/macro_test.rs index c15b2734353..2d40601fcd0 100644 --- a/tests/macro_test.rs +++ b/crates/macro/tests/macro_test.rs @@ -1,5 +1,5 @@ #[allow(dead_code)] -#[rustversion::attr(stable(1.41), cfg_attr(not(feature = "web_test"), test))] +#[rustversion::attr(stable(1.41), test)] fn tests() { let t = trybuild::TestCases::new(); diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 1b387cb9501..a2664b1dfee 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,28 +1,38 @@ [workspace] members = [ - "counter", "crm", "custom_components", "dashboard", - "node_refs", - "file_upload", "fragments", "futures", "game_of_life", - "inner_html", - "js_callback", "large_table", "minimal", - "mount_point", - "multi_thread", "nested_list", - "npm_and_rest", + "pub_sub", "server", "showcase", "textarea", "timer", - "todomvc", - "two_apps", - "pub_sub", - "webgl" + "webgl", + "std_web/counter", + "std_web/file_upload", + "std_web/inner_html", + "std_web/js_callback", + "std_web/mount_point", + "std_web/multi_thread", + "std_web/node_refs", + "std_web/npm_and_rest", + "std_web/todomvc", + "std_web/two_apps", + "web_sys/counter", + "web_sys/file_upload", + "web_sys/inner_html", + "web_sys/js_callback", + "web_sys/mount_point", + "web_sys/multi_thread", + "web_sys/node_refs", + "web_sys/npm_and_rest", + "web_sys/todomvc", + "web_sys/two_apps", ] diff --git a/examples/build_all.sh b/examples/build_all.sh index bd074fa57d5..15696561fe3 100755 --- a/examples/build_all.sh +++ b/examples/build_all.sh @@ -7,31 +7,102 @@ function ctrl_c() { kill $PID } -function build() { +function build_std_web() { for example in */ ; do if [[ $example == server* ]]; then continue fi - echo "Building: $example" - cd $example - cargo update - cargo web build --target wasm32-unknown-unknown - cd .. + if [[ $example == static* ]]; then + continue + fi + if [[ $example == web_sys* ]]; then + continue + fi + if [[ $example == std_web* ]]; then + build_std_web() + else + echo "Building: $example" + cd $example + cargo update + cargo web build --target wasm32-unknown-unknown + cd .. + fi + done +} + +function build_web_sys() { + for example in */ ; do + if [[ $example == server* ]]; then + continue + fi + if [[ $example == static* ]]; then + continue + fi + if [[ $example == std_web* ]]; then + continue + fi + if [[ $example == web_sys* ]]; then + build_web_sys() + else + echo "Building: $example" + cd $example + cargo update + cargo build --target wasm32-unknown-unknown + wasm-bindgen --target web --no-typescript --out-dir ../static/ --out-name wasm ../target/wasm32-unknown-unknown/debug/$example.wasm + cd .. + fi done } -function run() { +function run_std_web() { trap ctrl_c INT for example in */ ; do if [[ $example == server* ]]; then continue fi - echo "Running: $example" - cd $example - cargo web start --target wasm32-unknown-unknown & - PID=$! - wait $PID - cd .. + if [[ $example == static* ]]; then + continue + fi + if [[ $example == web_sys* ]]; then + continue + fi + if [[ $example == std_web* ]]; then + run_std_web() + else + echo "Running: $example" + cd $example + cargo web start --target wasm32-unknown-unknown & + PID=$! + wait $PID + cd .. + fi + done +} + +function run_web_sys() { + trap ctrl_c INT + for example in */ ; do + if [[ $example == server* ]]; then + continue + fi + if [[ $example == static* ]]; then + continue + fi + if [[ $example == std_web* ]]; then + continue + fi + if [[ $example == web_sys* ]]; then + run_web_sys() + else + echo "Running: $example" + cd $example + cargo build --target wasm32-unknown-unknown + wasm-bindgen --target web --no-typescript --out-dir ../static/ --out-name wasm ../target/wasm32-unknown-unknown/debug/$example.wasm + http -r ../static/ + PID=$! + wait $PID + cd .. + fi done } @@ -51,16 +122,19 @@ case "$1" in --help) echo "Available commands: build, run, clean" ;; - build) - build + build_std_web) + build_std_web + ;; + build_web_sys) + build_web_sys ;; - run) - run + run_std_web) + run_std_web + ;; + run_web_sys) + run_web_sys ;; clean) clean ;; - *) - build - ;; esac diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs deleted file mode 100644 index fa550b8b389..00000000000 --- a/examples/counter/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - yew::start_app::(); -} diff --git a/examples/crm/Cargo.toml b/examples/crm/Cargo.toml index 85a4e28e8d8..248a56c9238 100644 --- a/examples/crm/Cargo.toml +++ b/examples/crm/Cargo.toml @@ -9,3 +9,7 @@ serde = "1" serde_derive = "1" yew = { path = "../.." } pulldown-cmark = "0.1.2" + +[features] +std_web = ["yew/std_web"] +web_sys = ["yew/web_sys"] diff --git a/examples/crm/src/lib.rs b/examples/crm/src/lib.rs index 227feef5bbe..4a9fafe4ea8 100644 --- a/examples/crm/src/lib.rs +++ b/examples/crm/src/lib.rs @@ -64,7 +64,7 @@ impl Component for Model { type Properties = (); fn create(_: Self::Properties, link: ComponentLink) -> Self { - let storage = StorageService::new(Area::Local); + let storage = StorageService::new(Area::Local).expect("storage was disabled by the user"); let Json(database) = storage.restore(KEY); let database = database.unwrap_or_else(|_| Database { clients: Vec::new(), diff --git a/examples/custom_components/Cargo.toml b/examples/custom_components/Cargo.toml index 0e50867b039..9c8843fc883 100644 --- a/examples/custom_components/Cargo.toml +++ b/examples/custom_components/Cargo.toml @@ -6,3 +6,7 @@ edition = "2018" [dependencies] yew = { path = "../.." } + +[features] +std_web = ["yew/std_web"] +web_sys = ["yew/web_sys"] diff --git a/examples/dashboard/Cargo.toml b/examples/dashboard/Cargo.toml index f4e0ddb55fd..88d57d1f47b 100644 --- a/examples/dashboard/Cargo.toml +++ b/examples/dashboard/Cargo.toml @@ -9,3 +9,7 @@ anyhow = "1" serde = "1" serde_derive = "1" yew = { path = "../..", features = ["toml"] } + +[features] +std_web = ["yew/std_web"] +web_sys = ["yew/web_sys"] diff --git a/examples/dashboard/src/lib.rs b/examples/dashboard/src/lib.rs index 2927666885f..0e0cfee3bd5 100644 --- a/examples/dashboard/src/lib.rs +++ b/examples/dashboard/src/lib.rs @@ -91,9 +91,9 @@ impl Model { ); let request = Request::get("/data.json").body(Nothing).unwrap(); if binary { - self.fetch_service.fetch_binary(request, callback) + self.fetch_service.fetch_binary(request, callback).unwrap() } else { - self.fetch_service.fetch(request, callback) + self.fetch_service.fetch(request, callback).unwrap() } } @@ -111,9 +111,9 @@ impl Model { ); let request = Request::get("/data.toml").body(Nothing).unwrap(); if binary { - self.fetch_service.fetch_binary(request, callback) + self.fetch_service.fetch_binary(request, callback).unwrap() } else { - self.fetch_service.fetch(request, callback) + self.fetch_service.fetch(request, callback).unwrap() } } } diff --git a/examples/file_upload/src/main.rs b/examples/file_upload/src/main.rs deleted file mode 100644 index 405b924110c..00000000000 --- a/examples/file_upload/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - yew::start_app::(); -} diff --git a/examples/fragments/Cargo.toml b/examples/fragments/Cargo.toml index 7f481560e68..5b4bceb7116 100644 --- a/examples/fragments/Cargo.toml +++ b/examples/fragments/Cargo.toml @@ -6,3 +6,7 @@ edition = "2018" [dependencies] yew = { path = "../.." } + +[features] +std_web = ["yew/std_web"] +web_sys = ["yew/web_sys"] diff --git a/examples/futures/Cargo.toml b/examples/futures/Cargo.toml index 71279cf3483..dfb5f10d24d 100644 --- a/examples/futures/Cargo.toml +++ b/examples/futures/Cargo.toml @@ -15,7 +15,7 @@ yew = { path = "../.." } wasm-bindgen-futures = "0.4.3" [dependencies.web-sys] -version = "0.3.30" +version = "0.3.35" features = [ 'Headers', 'Request', @@ -27,3 +27,7 @@ features = [ [target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies] wasm-bindgen = "0.2.58" + +[features] +std_web = ["yew/std_web"] +web_sys = ["yew/web_sys"] diff --git a/examples/futures/README.md b/examples/futures/README.md index 9ef8e682c1e..9e5c9549ede 100644 --- a/examples/futures/README.md +++ b/examples/futures/README.md @@ -7,8 +7,8 @@ Because this example uses features not allowed by cargo web, it cannot be includ This example requires rustc v1.39.0 or above to compile due to its use of async/.await syntax. ```sh -wasm-pack build --target web && rollup ./main.js --format iife --file ./pkg/bundle.js && python -m SimpleHTTPServer 8080 +wasm-pack build --target web --out-dir ../static/ --out-name wasm -- --features (web_sys|std_web) && python -m SimpleHTTPServer 8080 ``` This will compile the project, bundle up the compiler output and static assets, and start a http server on port 8080 so you can access the example at localhost:8080. -It is expected that you have a setup with wasm-pack, rollup, and python installed. +It is expected that you have a setup with wasm-pack and python installed. diff --git a/examples/futures/index.html b/examples/futures/index.html deleted file mode 100644 index 61a5d46b81f..00000000000 --- a/examples/futures/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Yew • Futures - - - - - \ No newline at end of file diff --git a/examples/futures/main.js b/examples/futures/main.js deleted file mode 100644 index cce343c6215..00000000000 --- a/examples/futures/main.js +++ /dev/null @@ -1,6 +0,0 @@ -import init, { run_app } from './pkg/futures.js'; -async function main() { - await init('./pkg/futures_bg.wasm'); - run_app(); -} -main() \ No newline at end of file diff --git a/examples/futures/src/lib.rs b/examples/futures/src/lib.rs index fc6ed10ed58..a7d25723703 100644 --- a/examples/futures/src/lib.rs +++ b/examples/futures/src/lib.rs @@ -136,7 +136,7 @@ impl Component for Model { } } -#[wasm_bindgen] +#[wasm_bindgen(start)] pub fn run_app() { yew::start_app::(); } diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml index 26677bce28c..a67d8ed4d0e 100644 --- a/examples/game_of_life/Cargo.toml +++ b/examples/game_of_life/Cargo.toml @@ -7,7 +7,11 @@ authors = ["Diego Cardoso ", edition = "2018" [dependencies] -rand = { version = "0.6.5", features = ["stdweb"] } +rand = "0.6.5" log = "0.4" -web_logger = "0.1" +web_logger = "0.2" yew = { path = "../.." } + +[features] +std_web = ["rand/stdweb", "yew/std_web"] +web_sys = ["rand/wasm-bindgen", "yew/web_sys"] diff --git a/examples/inner_html/Cargo.toml b/examples/inner_html/Cargo.toml deleted file mode 100644 index 37c92c05b64..00000000000 --- a/examples/inner_html/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "inner_html" -version = "0.1.0" -authors = ["Garrett Berg "] -edition = "2018" - -[dependencies] -stdweb = "0.4.20" -yew = { path = "../.." } diff --git a/examples/js_callback/src/main.rs b/examples/js_callback/src/main.rs deleted file mode 100644 index b03f7331e40..00000000000 --- a/examples/js_callback/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - yew::start_app::(); -} diff --git a/examples/large_table/Cargo.toml b/examples/large_table/Cargo.toml index 67d7196cede..b62c39ea019 100644 --- a/examples/large_table/Cargo.toml +++ b/examples/large_table/Cargo.toml @@ -6,3 +6,7 @@ edition = "2018" [dependencies] yew = { path = "../.." } + +[features] +std_web = ["yew/std_web"] +web_sys = ["yew/web_sys"] diff --git a/examples/minimal/Cargo.toml b/examples/minimal/Cargo.toml index ce3b9cdc1dc..be969b88ecb 100644 --- a/examples/minimal/Cargo.toml +++ b/examples/minimal/Cargo.toml @@ -6,3 +6,7 @@ edition = "2018" [dependencies] yew = { path = "../.." } + +[features] +std_web = ["yew/std_web"] +web_sys = ["yew/web_sys"] diff --git a/examples/mount_point/Cargo.toml b/examples/mount_point/Cargo.toml deleted file mode 100644 index 675b6281a5b..00000000000 --- a/examples/mount_point/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "mount_point" -version = "0.1.0" -authors = ["Ben Berman "] -edition = "2018" - -[dependencies] -stdweb = "0.4.20" -yew = { path = "../.." } diff --git a/examples/multi_thread/src/bin/main.rs b/examples/multi_thread/src/bin/main.rs deleted file mode 100644 index 1a699be56aa..00000000000 --- a/examples/multi_thread/src/bin/main.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - web_logger::init(); - yew::start_app::(); -} diff --git a/examples/multi_thread/static/bin b/examples/multi_thread/static/bin deleted file mode 120000 index c01ec7e5dfe..00000000000 --- a/examples/multi_thread/static/bin +++ /dev/null @@ -1 +0,0 @@ -../../target/wasm32-unknown-unknown/release \ No newline at end of file diff --git a/examples/nested_list/Cargo.toml b/examples/nested_list/Cargo.toml index 744148fd54b..6dc090c3555 100644 --- a/examples/nested_list/Cargo.toml +++ b/examples/nested_list/Cargo.toml @@ -8,3 +8,7 @@ edition = "2018" log = "0.4" web_logger = "0.2" yew = { path = "../.." } + +[features] +std_web = ["yew/std_web"] +web_sys = ["yew/web_sys"] diff --git a/examples/node_refs/src/main.rs b/examples/node_refs/src/main.rs deleted file mode 100644 index 63360e2c5d9..00000000000 --- a/examples/node_refs/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - yew::start_app::(); -} diff --git a/examples/npm_and_rest/src/main.rs b/examples/npm_and_rest/src/main.rs deleted file mode 100644 index 8e3af40a3a9..00000000000 --- a/examples/npm_and_rest/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - yew::start_app::(); -} diff --git a/examples/showcase/Cargo.toml b/examples/showcase/Cargo.toml index b9acad62070..eb6c311f2b4 100644 --- a/examples/showcase/Cargo.toml +++ b/examples/showcase/Cargo.toml @@ -5,23 +5,69 @@ authors = ["Denis Kolodin ", "Limira"] edition = "2018" [dependencies] +cfg-if = "0.1" log = "0.4" -web_logger = "0.1" +web_logger = "0.2" strum = "0.13" strum_macros = "0.13" yew = { path = "../.." } -counter = { path = "../counter" } +counter_std_web = { path = "../std_web/counter", optional = true } +counter_web_sys = { path = "../web_sys/counter", optional = true } crm = { path = "../crm" } custom_components = { path = "../custom_components" } dashboard = { path = "../dashboard" } -node_refs = { path = "../node_refs" } +node_refs_std_web = { path = "../std_web/node_refs", optional = true } +node_refs_web_sys = { path = "../web_sys/node_refs", optional = true } fragments = { path = "../fragments" } game_of_life = { path = "../game_of_life" } -inner_html = { path = "../inner_html" } +inner_html_std_web = { path = "../std_web/inner_html", optional = true } +inner_html_web_sys = { path = "../web_sys/inner_html", optional = true } large_table = { path = "../large_table" } -mount_point = { path = "../mount_point" } -npm_and_rest = { path = "../npm_and_rest" } +mount_point_std_web = { path = "../std_web/mount_point", optional = true } +mount_point_web_sys = { path = "../web_sys/mount_point", optional = true } +npm_and_rest_std_web = { path = "../std_web/npm_and_rest", optional = true } +npm_and_rest_web_sys = { path = "../web_sys/npm_and_rest", optional = true } textarea = { path = "../textarea" } timer = { path = "../timer" } -todomvc = { path = "../todomvc" } -two_apps = { path = "../two_apps" } +todomvc_std_web = { path = "../std_web/todomvc", optional = true } +todomvc_web_sys = { path = "../web_sys/todomvc", optional = true } +two_apps_std_web = { path = "../std_web/two_apps", optional = true } +two_apps_web_sys = { path = "../web_sys/two_apps", optional = true } + +[features] +std_web = [ + "yew/std_web", + "counter_std_web", + "crm/std_web", + "custom_components/std_web", + "dashboard/std_web", + "node_refs_std_web", + "fragments/std_web", + "game_of_life/std_web", + "inner_html_std_web", + "large_table/std_web", + "mount_point_std_web", + "npm_and_rest_std_web", + "textarea/std_web", + "timer/std_web", + "todomvc_std_web", + "two_apps_std_web", +] +web_sys = [ + "yew/web_sys", + "counter_web_sys", + "crm/web_sys", + "custom_components/web_sys", + "dashboard/web_sys", + "node_refs_web_sys", + "fragments/web_sys", + "game_of_life/web_sys", + "inner_html_web_sys", + "large_table/web_sys", + "mount_point_web_sys", + "npm_and_rest_web_sys", + "textarea/web_sys", + "timer/web_sys", + "todomvc_web_sys", + "two_apps_web_sys", +] diff --git a/examples/showcase/src/main.rs b/examples/showcase/src/main.rs index d2b0329fd7b..d4b31da07e0 100644 --- a/examples/showcase/src/main.rs +++ b/examples/showcase/src/main.rs @@ -1,5 +1,25 @@ #![recursion_limit = "128"] +cfg_if::cfg_if! { + if #[cfg(feature = "std_web")] { + use counter_std_web as counter; + use inner_html_std_web as inner_html; + use mount_point_std_web as mount_point; + use node_refs_std_web as node_refs; + use npm_and_rest_std_web as npm_and_rest; + use todomvc_std_web as todomvc; + use two_apps_std_web as two_apps; + } else if #[cfg(feature = "web_sys")] { + use counter_web_sys as counter; + use inner_html_web_sys as inner_html; + use mount_point_web_sys as mount_point; + use node_refs_web_sys as node_refs; + use npm_and_rest_web_sys as npm_and_rest; + use todomvc_web_sys as todomvc; + use two_apps_web_sys as two_apps; + } +} + use counter::Model as Counter; use crm::Model as Crm; use custom_components::Model as CustomComponents; diff --git a/examples/static/.gitignore b/examples/static/.gitignore new file mode 100644 index 00000000000..7ad566ec26a --- /dev/null +++ b/examples/static/.gitignore @@ -0,0 +1,3 @@ +*.wasm +*.js +snippets/ diff --git a/examples/static/index.html b/examples/static/index.html new file mode 100644 index 00000000000..e1925eb8819 --- /dev/null +++ b/examples/static/index.html @@ -0,0 +1,12 @@ + + + + + Yew example + + + + diff --git a/examples/counter/Cargo.toml b/examples/std_web/counter/Cargo.toml similarity index 59% rename from examples/counter/Cargo.toml rename to examples/std_web/counter/Cargo.toml index 22272744d51..0c6e1418de9 100644 --- a/examples/counter/Cargo.toml +++ b/examples/std_web/counter/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "counter" +name = "counter_std_web" version = "0.1.1" authors = ["Denis Kolodin "] edition = "2018" [dependencies] stdweb = "0.4.20" -yew = { path = "../.." } +yew = { path = "../../..", features = ["services", "std_web"] } diff --git a/examples/counter/src/lib.rs b/examples/std_web/counter/src/lib.rs similarity index 98% rename from examples/counter/src/lib.rs rename to examples/std_web/counter/src/lib.rs index fa9d33de841..430d9e6ad9e 100644 --- a/examples/counter/src/lib.rs +++ b/examples/std_web/counter/src/lib.rs @@ -1,4 +1,4 @@ -#![recursion_limit = "128"] +#![recursion_limit = "256"] use stdweb::web::Date; use yew::services::ConsoleService; diff --git a/examples/std_web/counter/src/main.rs b/examples/std_web/counter/src/main.rs new file mode 100644 index 00000000000..e3193531fdf --- /dev/null +++ b/examples/std_web/counter/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/file_upload/Cargo.toml b/examples/std_web/file_upload/Cargo.toml similarity index 58% rename from examples/file_upload/Cargo.toml rename to examples/std_web/file_upload/Cargo.toml index 84d09fe47cc..2a2c8bc7564 100644 --- a/examples/file_upload/Cargo.toml +++ b/examples/std_web/file_upload/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "file_upload" +name = "file_upload_std_web" version = "0.1.0" authors = ["Denis Kolodin "] edition = "2018" [dependencies] -yew = { path = "../.." } +yew = { path = "../../..", features = ["std_web"] } diff --git a/examples/file_upload/src/lib.rs b/examples/std_web/file_upload/src/lib.rs similarity index 98% rename from examples/file_upload/src/lib.rs rename to examples/std_web/file_upload/src/lib.rs index 9f909331434..b0eee363402 100644 --- a/examples/file_upload/src/lib.rs +++ b/examples/std_web/file_upload/src/lib.rs @@ -49,10 +49,10 @@ impl Component for Model { let task = { if chunks { let callback = self.link.callback(Msg::Chunk); - self.reader.read_file_by_chunks(file, callback, 10) + self.reader.read_file_by_chunks(file, callback, 10).unwrap() } else { let callback = self.link.callback(Msg::Loaded); - self.reader.read_file(file, callback) + self.reader.read_file(file, callback).unwrap() } }; self.tasks.push(task); diff --git a/examples/std_web/file_upload/src/main.rs b/examples/std_web/file_upload/src/main.rs new file mode 100644 index 00000000000..c5578f8e78d --- /dev/null +++ b/examples/std_web/file_upload/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/std_web/inner_html/Cargo.toml b/examples/std_web/inner_html/Cargo.toml new file mode 100644 index 00000000000..05d0ec776c2 --- /dev/null +++ b/examples/std_web/inner_html/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "inner_html_std_web" +version = "0.1.0" +authors = ["Garrett Berg "] +edition = "2018" + +[dependencies] +stdweb = "0.4.20" +yew = { path = "../../..", features = ["std_web"] } + +[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies] +wasm-bindgen = "0.2.58" diff --git a/examples/inner_html/src/lib.rs b/examples/std_web/inner_html/src/lib.rs similarity index 100% rename from examples/inner_html/src/lib.rs rename to examples/std_web/inner_html/src/lib.rs diff --git a/examples/inner_html/src/main.rs b/examples/std_web/inner_html/src/main.rs similarity index 100% rename from examples/inner_html/src/main.rs rename to examples/std_web/inner_html/src/main.rs diff --git a/examples/js_callback/Cargo.toml b/examples/std_web/js_callback/Cargo.toml similarity index 73% rename from examples/js_callback/Cargo.toml rename to examples/std_web/js_callback/Cargo.toml index 242701f9958..e1d0d931bb0 100644 --- a/examples/js_callback/Cargo.toml +++ b/examples/std_web/js_callback/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "js_callback" +name = "js_callback_std_web" version = "0.1.0" authors = ["Scott Steele "] edition = "2018" [dependencies] -yew = { path = "../.." } +yew = { path = "../../..", features = ["std_web"] } stdweb = "^0.4.20" [target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies] diff --git a/examples/js_callback/README.md b/examples/std_web/js_callback/README.md similarity index 100% rename from examples/js_callback/README.md rename to examples/std_web/js_callback/README.md diff --git a/examples/js_callback/src/lib.rs b/examples/std_web/js_callback/src/lib.rs similarity index 100% rename from examples/js_callback/src/lib.rs rename to examples/std_web/js_callback/src/lib.rs diff --git a/examples/std_web/js_callback/src/main.rs b/examples/std_web/js_callback/src/main.rs new file mode 100644 index 00000000000..1c4ab7f0979 --- /dev/null +++ b/examples/std_web/js_callback/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/js_callback/static/get-payload-script.js b/examples/std_web/js_callback/static/get-payload-script.js similarity index 100% rename from examples/js_callback/static/get-payload-script.js rename to examples/std_web/js_callback/static/get-payload-script.js diff --git a/examples/js_callback/static/index.html b/examples/std_web/js_callback/static/index.html similarity index 100% rename from examples/js_callback/static/index.html rename to examples/std_web/js_callback/static/index.html diff --git a/examples/std_web/mount_point/Cargo.toml b/examples/std_web/mount_point/Cargo.toml new file mode 100644 index 00000000000..e3cb5a7bd56 --- /dev/null +++ b/examples/std_web/mount_point/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "mount_point_std_web" +version = "0.1.0" +authors = ["Ben Berman "] +edition = "2018" + +[dependencies] +stdweb = "0.4.20" +yew = { path = "../../..", features = ["std_web"] } + +[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies] +wasm-bindgen = "0.2.58" diff --git a/examples/mount_point/src/lib.rs b/examples/std_web/mount_point/src/lib.rs similarity index 100% rename from examples/mount_point/src/lib.rs rename to examples/std_web/mount_point/src/lib.rs diff --git a/examples/mount_point/src/main.rs b/examples/std_web/mount_point/src/main.rs similarity index 96% rename from examples/mount_point/src/main.rs rename to examples/std_web/mount_point/src/main.rs index 2253a36f26d..57c144ea65f 100644 --- a/examples/mount_point/src/main.rs +++ b/examples/std_web/mount_point/src/main.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate stdweb; -use mount_point::Model; +use mount_point_std_web::Model; use stdweb::web::{document, IElement, INode, IParentNode}; use yew::App; diff --git a/examples/multi_thread/Cargo.toml b/examples/std_web/multi_thread/Cargo.toml similarity index 73% rename from examples/multi_thread/Cargo.toml rename to examples/std_web/multi_thread/Cargo.toml index 1841175d9b6..89d9ebe3f39 100644 --- a/examples/multi_thread/Cargo.toml +++ b/examples/std_web/multi_thread/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "multi_thread" +name = "multi_thread_std_web" version = "0.1.0" authors = ["Denis Kolodin "] edition = "2018" @@ -14,7 +14,7 @@ path = "src/bin/native_worker.rs" [dependencies] log = "0.4" -web_logger = "0.1" +web_logger = "0.2" serde = "1.0" serde_derive = "1.0" -yew = { path = "../.." } +yew = { path = "../../..", features = ["std_web"] } diff --git a/examples/multi_thread/README.md b/examples/std_web/multi_thread/README.md similarity index 61% rename from examples/multi_thread/README.md rename to examples/std_web/multi_thread/README.md index 32da696e6c6..79abcf6516d 100644 --- a/examples/multi_thread/README.md +++ b/examples/std_web/multi_thread/README.md @@ -3,5 +3,5 @@ You should compile a worker which have to be spawned in a separate thread: ```sh -cargo web build --bin native_worker --release +cargo web build --bin native_worker --release --features std_web ``` diff --git a/examples/multi_thread/Web.toml b/examples/std_web/multi_thread/Web.toml similarity index 100% rename from examples/multi_thread/Web.toml rename to examples/std_web/multi_thread/Web.toml diff --git a/examples/std_web/multi_thread/src/bin/main.rs b/examples/std_web/multi_thread/src/bin/main.rs new file mode 100644 index 00000000000..c06e491d700 --- /dev/null +++ b/examples/std_web/multi_thread/src/bin/main.rs @@ -0,0 +1,4 @@ +fn main() { + web_logger::init(); + yew::start_app::(); +} diff --git a/examples/multi_thread/src/bin/native_worker.rs b/examples/std_web/multi_thread/src/bin/native_worker.rs similarity index 64% rename from examples/multi_thread/src/bin/native_worker.rs rename to examples/std_web/multi_thread/src/bin/native_worker.rs index f4343ca2755..9886831fc6d 100644 --- a/examples/multi_thread/src/bin/native_worker.rs +++ b/examples/std_web/multi_thread/src/bin/native_worker.rs @@ -3,6 +3,6 @@ use yew::agent::Threaded; fn main() { web_logger::init(); yew::initialize(); - multi_thread::native_worker::Worker::register(); + multi_thread_std_web::native_worker::Worker::register(); yew::run_loop(); } diff --git a/examples/multi_thread/src/context.rs b/examples/std_web/multi_thread/src/context.rs similarity index 100% rename from examples/multi_thread/src/context.rs rename to examples/std_web/multi_thread/src/context.rs diff --git a/examples/multi_thread/src/job.rs b/examples/std_web/multi_thread/src/job.rs similarity index 100% rename from examples/multi_thread/src/job.rs rename to examples/std_web/multi_thread/src/job.rs diff --git a/examples/multi_thread/src/lib.rs b/examples/std_web/multi_thread/src/lib.rs similarity index 100% rename from examples/multi_thread/src/lib.rs rename to examples/std_web/multi_thread/src/lib.rs diff --git a/examples/multi_thread/src/native_worker.rs b/examples/std_web/multi_thread/src/native_worker.rs similarity index 100% rename from examples/multi_thread/src/native_worker.rs rename to examples/std_web/multi_thread/src/native_worker.rs diff --git a/examples/std_web/multi_thread/static/bin b/examples/std_web/multi_thread/static/bin new file mode 100644 index 00000000000..4a2105e009b --- /dev/null +++ b/examples/std_web/multi_thread/static/bin @@ -0,0 +1 @@ +../../../target/wasm32-unknown-unknown/release \ No newline at end of file diff --git a/examples/multi_thread/static/index.html b/examples/std_web/multi_thread/static/index.html similarity index 100% rename from examples/multi_thread/static/index.html rename to examples/std_web/multi_thread/static/index.html diff --git a/examples/node_refs/Cargo.toml b/examples/std_web/node_refs/Cargo.toml similarity index 62% rename from examples/node_refs/Cargo.toml rename to examples/std_web/node_refs/Cargo.toml index 2cb0ce4acfd..1ecf044a350 100644 --- a/examples/node_refs/Cargo.toml +++ b/examples/std_web/node_refs/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "node_refs" +name = "node_refs_std_web" version = "0.1.0" authors = ["Justin Starry "] edition = "2018" [dependencies] -yew = { path = "../.." } +yew = { path = "../../..", features = ["std_web"] } stdweb = "0.4.20" diff --git a/examples/node_refs/src/input.rs b/examples/std_web/node_refs/src/input.rs similarity index 100% rename from examples/node_refs/src/input.rs rename to examples/std_web/node_refs/src/input.rs diff --git a/examples/node_refs/src/lib.rs b/examples/std_web/node_refs/src/lib.rs similarity index 100% rename from examples/node_refs/src/lib.rs rename to examples/std_web/node_refs/src/lib.rs diff --git a/examples/std_web/node_refs/src/main.rs b/examples/std_web/node_refs/src/main.rs new file mode 100644 index 00000000000..9c0f53346bf --- /dev/null +++ b/examples/std_web/node_refs/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/std_web/npm_and_rest/Cargo.toml b/examples/std_web/npm_and_rest/Cargo.toml new file mode 100644 index 00000000000..fcfcf3f49d6 --- /dev/null +++ b/examples/std_web/npm_and_rest/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "npm_and_rest_std_web" +version = "0.1.0" +authors = ["Denis Kolodin "] +edition = "2018" + +[dependencies] +anyhow = "1" +serde = "1" +serde_derive = "1" +stdweb = "0.4.20" +yew = { path = "../../..", features = ["std_web"] } + +[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies] +wasm-bindgen = "0.2.58" \ No newline at end of file diff --git a/examples/npm_and_rest/src/ccxt.rs b/examples/std_web/npm_and_rest/src/ccxt.rs similarity index 100% rename from examples/npm_and_rest/src/ccxt.rs rename to examples/std_web/npm_and_rest/src/ccxt.rs diff --git a/examples/npm_and_rest/src/gravatar.rs b/examples/std_web/npm_and_rest/src/gravatar.rs similarity index 95% rename from examples/npm_and_rest/src/gravatar.rs rename to examples/std_web/npm_and_rest/src/gravatar.rs index 2dafae95aa6..72c035852ad 100644 --- a/examples/npm_and_rest/src/gravatar.rs +++ b/examples/std_web/npm_and_rest/src/gravatar.rs @@ -45,6 +45,6 @@ impl GravatarService { } }; let request = Request::get(url.as_str()).body(Nothing).unwrap(); - self.web.fetch(request, handler.into()) + self.web.fetch(request, handler.into()).unwrap() } } diff --git a/examples/npm_and_rest/src/lib.rs b/examples/std_web/npm_and_rest/src/lib.rs similarity index 100% rename from examples/npm_and_rest/src/lib.rs rename to examples/std_web/npm_and_rest/src/lib.rs diff --git a/examples/std_web/npm_and_rest/src/main.rs b/examples/std_web/npm_and_rest/src/main.rs new file mode 100644 index 00000000000..267c8afdfcf --- /dev/null +++ b/examples/std_web/npm_and_rest/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/npm_and_rest/static/index.html b/examples/std_web/npm_and_rest/static/index.html similarity index 100% rename from examples/npm_and_rest/static/index.html rename to examples/std_web/npm_and_rest/static/index.html diff --git a/examples/todomvc/Cargo.toml b/examples/std_web/todomvc/Cargo.toml similarity index 70% rename from examples/todomvc/Cargo.toml rename to examples/std_web/todomvc/Cargo.toml index 9b71961e468..a230bebe475 100644 --- a/examples/todomvc/Cargo.toml +++ b/examples/std_web/todomvc/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "todomvc" +name = "todomvc_std_web" version = "0.1.0" authors = ["Denis Kolodin "] edition = "2018" @@ -9,4 +9,4 @@ strum = "0.13" strum_macros = "0.13" serde = "1" serde_derive = "1" -yew = { path = "../.." } +yew = { path = "../../..", features = ["std_web"] } diff --git a/examples/todomvc/README.md b/examples/std_web/todomvc/README.md similarity index 100% rename from examples/todomvc/README.md rename to examples/std_web/todomvc/README.md diff --git a/examples/todomvc/src/lib.rs b/examples/std_web/todomvc/src/lib.rs similarity index 99% rename from examples/todomvc/src/lib.rs rename to examples/std_web/todomvc/src/lib.rs index 71113feef31..dffea6676e1 100644 --- a/examples/todomvc/src/lib.rs +++ b/examples/std_web/todomvc/src/lib.rs @@ -50,7 +50,7 @@ impl Component for Model { type Properties = (); fn create(_: Self::Properties, link: ComponentLink) -> Self { - let storage = StorageService::new(Area::Local); + let storage = StorageService::new(Area::Local).expect("storage was disabled by the user"); let entries = { if let Json(Ok(restored_model)) = storage.restore(KEY) { restored_model diff --git a/examples/std_web/todomvc/src/main.rs b/examples/std_web/todomvc/src/main.rs new file mode 100644 index 00000000000..acea50454b1 --- /dev/null +++ b/examples/std_web/todomvc/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/todomvc/static/index.html b/examples/std_web/todomvc/static/index.html similarity index 100% rename from examples/todomvc/static/index.html rename to examples/std_web/todomvc/static/index.html diff --git a/examples/two_apps/Cargo.toml b/examples/std_web/two_apps/Cargo.toml similarity index 62% rename from examples/two_apps/Cargo.toml rename to examples/std_web/two_apps/Cargo.toml index d080b683938..65effeeee88 100644 --- a/examples/two_apps/Cargo.toml +++ b/examples/std_web/two_apps/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "two_apps" +name = "two_apps_std_web" version = "0.1.0" authors = ["Denis Kolodin "] edition = "2018" [dependencies] stdweb = "0.4.20" -yew = { path = "../.." } +yew = { path = "../../..", features = ["std_web"] } diff --git a/examples/two_apps/src/lib.rs b/examples/std_web/two_apps/src/lib.rs similarity index 100% rename from examples/two_apps/src/lib.rs rename to examples/std_web/two_apps/src/lib.rs diff --git a/examples/std_web/two_apps/src/main.rs b/examples/std_web/two_apps/src/main.rs new file mode 100644 index 00000000000..c7176a9369d --- /dev/null +++ b/examples/std_web/two_apps/src/main.rs @@ -0,0 +1,21 @@ +use stdweb::web::IParentNode; +use two_apps_std_web::{Model, Msg}; +use yew::html::Scope; +use yew::App; + +fn mount_app(selector: &'static str, app: App) -> Scope { + let document = yew::utils::document(); + let element = document.query_selector(selector).unwrap().unwrap(); + app.mount(element) +} + +fn main() { + yew::initialize(); + let first_app = App::new(); + let second_app = App::new(); + let to_first = mount_app(".first-app", first_app); + let to_second = mount_app(".second-app", second_app); + to_first.send_message(Msg::SetScope(to_second.clone())); + to_second.send_message(Msg::SetScope(to_first.clone())); + yew::run_loop(); +} diff --git a/examples/two_apps/static/index.html b/examples/std_web/two_apps/static/index.html similarity index 100% rename from examples/two_apps/static/index.html rename to examples/std_web/two_apps/static/index.html diff --git a/examples/textarea/Cargo.toml b/examples/textarea/Cargo.toml index 2f892e7bed5..00e96d6494a 100644 --- a/examples/textarea/Cargo.toml +++ b/examples/textarea/Cargo.toml @@ -6,3 +6,7 @@ edition = "2018" [dependencies] yew = { path = "../.." } + +[features] +std_web = ["yew/std_web"] +web_sys = ["yew/web_sys"] diff --git a/examples/timer/Cargo.toml b/examples/timer/Cargo.toml index 8d1e74d3df2..ca417b914c5 100644 --- a/examples/timer/Cargo.toml +++ b/examples/timer/Cargo.toml @@ -6,3 +6,7 @@ edition = "2018" [dependencies] yew = { path = "../.." } + +[features] +std_web = ["yew/std_web"] +web_sys = ["yew/web_sys"] diff --git a/examples/todomvc/src/main.rs b/examples/todomvc/src/main.rs deleted file mode 100644 index c45e29a36fa..00000000000 --- a/examples/todomvc/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - yew::start_app::(); -} diff --git a/examples/web_sys/counter/Cargo.toml b/examples/web_sys/counter/Cargo.toml new file mode 100644 index 00000000000..3e687d650a1 --- /dev/null +++ b/examples/web_sys/counter/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "counter_web_sys" +version = "0.1.1" +authors = ["Denis Kolodin "] +edition = "2018" + +[dependencies] +js-sys = "0.3" +yew = { path = "../../..", features = ["services", "web_sys"] } diff --git a/examples/web_sys/counter/src/lib.rs b/examples/web_sys/counter/src/lib.rs new file mode 100644 index 00000000000..7bf99358801 --- /dev/null +++ b/examples/web_sys/counter/src/lib.rs @@ -0,0 +1,70 @@ +#![recursion_limit = "256"] + +use js_sys::Date; +use yew::services::ConsoleService; +use yew::{html, Component, ComponentLink, Html, ShouldRender}; + +pub struct Model { + link: ComponentLink, + console: ConsoleService, + value: i64, +} + +pub enum Msg { + Increment, + Decrement, + Bulk(Vec), +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { + link, + console: ConsoleService::new(), + value: 0, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::Increment => { + self.value = self.value + 1; + self.console.log("plus one"); + } + Msg::Decrement => { + self.value = self.value - 1; + self.console.log("minus one"); + } + Msg::Bulk(list) => { + for msg in list { + self.update(msg); + self.console.log("Bulk action"); + } + } + } + true + } + + fn view(&self) -> Html { + html! { +
+ +

{ self.value }

+

{ Date::new_0().to_string().as_string().unwrap() }

+
+ } + } +} diff --git a/examples/web_sys/counter/src/main.rs b/examples/web_sys/counter/src/main.rs new file mode 100644 index 00000000000..88e374caeca --- /dev/null +++ b/examples/web_sys/counter/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/web_sys/file_upload/Cargo.toml b/examples/web_sys/file_upload/Cargo.toml new file mode 100644 index 00000000000..afee9936ec6 --- /dev/null +++ b/examples/web_sys/file_upload/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "file_upload_web_sys" +version = "0.1.0" +authors = ["Denis Kolodin "] +edition = "2018" + +[dependencies] +js-sys = "0.3" +yew = { path = "../../..", features = ["web_sys"] } diff --git a/examples/web_sys/file_upload/src/lib.rs b/examples/web_sys/file_upload/src/lib.rs new file mode 100644 index 00000000000..5d1dcd005c8 --- /dev/null +++ b/examples/web_sys/file_upload/src/lib.rs @@ -0,0 +1,104 @@ +#![recursion_limit = "256"] + +use yew::services::reader::{File, FileChunk, FileData, ReaderService, ReaderTask}; +use yew::{html, ChangeData, Component, ComponentLink, Html, ShouldRender}; + +pub struct Model { + link: ComponentLink, + reader: ReaderService, + tasks: Vec, + files: Vec, + by_chunks: bool, +} + +type Chunks = bool; + +pub enum Msg { + Loaded(FileData), + Chunk(FileChunk), + Files(Vec, Chunks), + ToggleByChunks, +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { + reader: ReaderService::new(), + link, + tasks: vec![], + files: vec![], + by_chunks: false, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::Loaded(file) => { + let info = format!("file: {:?}", file); + self.files.push(info); + } + Msg::Chunk(chunk) => { + let info = format!("chunk: {:?}", chunk); + self.files.push(info); + } + Msg::Files(files, chunks) => { + for file in files.into_iter() { + let task = { + if chunks { + let callback = self.link.callback(Msg::Chunk); + self.reader.read_file_by_chunks(file, callback, 10).unwrap() + } else { + let callback = self.link.callback(Msg::Loaded); + self.reader.read_file(file, callback).unwrap() + } + }; + self.tasks.push(task); + } + } + Msg::ToggleByChunks => { + self.by_chunks = !self.by_chunks; + } + } + true + } + + fn view(&self) -> Html { + let flag = self.by_chunks; + html! { +
+
+ +
+
+ + +
+
    + { for self.files.iter().map(|f| self.view_file(f)) } +
+
+ } + } +} + +impl Model { + fn view_file(&self, data: &str) -> Html { + html! { +
  • { data }
  • + } + } +} diff --git a/examples/web_sys/file_upload/src/main.rs b/examples/web_sys/file_upload/src/main.rs new file mode 100644 index 00000000000..c902af4d408 --- /dev/null +++ b/examples/web_sys/file_upload/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/web_sys/inner_html/Cargo.toml b/examples/web_sys/inner_html/Cargo.toml new file mode 100644 index 00000000000..70d8a952bcc --- /dev/null +++ b/examples/web_sys/inner_html/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "inner_html_web_sys" +version = "0.1.0" +authors = ["Garrett Berg "] +edition = "2018" + +[dependencies] +web-sys = { version = "0.3", features = ["console", "Document", "Element", "Node", "Window"] } +yew = { path = "../../..", features = ["web_sys"] } diff --git a/examples/web_sys/inner_html/src/lib.rs b/examples/web_sys/inner_html/src/lib.rs new file mode 100644 index 00000000000..160290adafe --- /dev/null +++ b/examples/web_sys/inner_html/src/lib.rs @@ -0,0 +1,51 @@ +#![recursion_limit = "512"] + +use web_sys::{console, Node}; +use yew::virtual_dom::VNode; +use yew::{Component, ComponentLink, Html, ShouldRender}; + +const SVG: &str = r#" +

    Inline SVG or any HTML:

    + + + Sorry, your browser does not support inline SVG. + +"#; + +pub struct Model { + pub value: i64, +} + +pub enum Msg {} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, _: ComponentLink) -> Self { + Model { value: 0 } + } + + fn update(&mut self, _: Self::Message) -> ShouldRender { + true + } + + fn view(&self) -> Html { + let js_svg = { + let div = web_sys::window() + .unwrap() + .document() + .unwrap() + .create_element("div") + .unwrap(); + div.set_inner_html(SVG); + console::log_1(&div); + div + }; + eprintln!("js_svg: {:?}", js_svg); + let node = Node::from(js_svg); + let vnode = VNode::VRef(node); + eprintln!("svg: {:?}", vnode); + vnode + } +} diff --git a/examples/web_sys/inner_html/src/main.rs b/examples/web_sys/inner_html/src/main.rs new file mode 100644 index 00000000000..e5c55fd9b98 --- /dev/null +++ b/examples/web_sys/inner_html/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/web_sys/js_callback/Cargo.toml b/examples/web_sys/js_callback/Cargo.toml new file mode 100644 index 00000000000..91413b7eb4c --- /dev/null +++ b/examples/web_sys/js_callback/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "js_callback_web_sys" +version = "0.1.0" +authors = ["Scott Steele "] +edition = "2018" + +[dependencies] +yew = { path = "../../..", features = ["web_sys"] } + +[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies] +wasm-bindgen = "0.2.58" diff --git a/examples/web_sys/js_callback/README.md b/examples/web_sys/js_callback/README.md new file mode 100644 index 00000000000..027f020adec --- /dev/null +++ b/examples/web_sys/js_callback/README.md @@ -0,0 +1,8 @@ +(Asynchronous) callback from Javascript +======================================= + +The purpose of this example is to demonstrate a simple case of asynchronously +sending a message back into the component update loop. + +See https://github.com/yewstack/yew/issues/316 for discussion on what +motivated this example. diff --git a/examples/web_sys/js_callback/src/lib.rs b/examples/web_sys/js_callback/src/lib.rs new file mode 100644 index 00000000000..d6e5bb73400 --- /dev/null +++ b/examples/web_sys/js_callback/src/lib.rs @@ -0,0 +1,98 @@ +#![recursion_limit = "128"] +#![deny(warnings)] + +use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsValue}; +use yew::prelude::*; + +pub struct Model { + payload: String, + // Pointless field just to have something that's been manipulated + debugged_payload: String, + link: ComponentLink, +} + +pub enum Msg { + Payload(String), + AsyncPayload, +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + let payload = String::default(); + let debugged_payload = format!("{:?}", payload); + Self { + payload, + debugged_payload, + link, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + use Msg::*; + match msg { + Payload(payload) => { + if payload != self.payload { + self.debugged_payload = format!("{:?}", payload); + self.payload = payload; + true + } else { + false + } + } + AsyncPayload => { + get_payload_later(self.link.callback(Msg::Payload)); + false + } + } + } + + fn change(&mut self, _: Self::Properties) -> ShouldRender { + false + } + + fn view(&self) -> Html { + html! { +
    + + + +

    + { nbsp(self.debugged_payload.as_str()) } +

    +
    + } + } +} + +fn nbsp(string: T) -> String +where + String: From, +{ + String::from(string).replace(' ', "\u{00a0}") +} + +#[wasm_bindgen] +extern "C" { + fn get_payload() -> String; +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_name = "get_payload_later")] + fn get_payload_later_js(payload_callback: JsValue); +} + +fn get_payload_later(payload_callback: Callback) { + let callback = Closure::once_into_js(move |payload: String| payload_callback.emit(payload)); + get_payload_later_js(callback); +} diff --git a/examples/web_sys/js_callback/src/main.rs b/examples/web_sys/js_callback/src/main.rs new file mode 100644 index 00000000000..db451555d85 --- /dev/null +++ b/examples/web_sys/js_callback/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/web_sys/js_callback/static/get-payload-script.js b/examples/web_sys/js_callback/static/get-payload-script.js new file mode 100644 index 00000000000..2735ee185c3 --- /dev/null +++ b/examples/web_sys/js_callback/static/get-payload-script.js @@ -0,0 +1,9 @@ +function get_payload() { + return (new Date()).toString() +} + +function get_payload_later(callback) { + setTimeout(() => { + callback(get_payload()) + }, 1000) +} diff --git a/examples/web_sys/js_callback/static/index.html b/examples/web_sys/js_callback/static/index.html new file mode 100644 index 00000000000..8e47c484fcc --- /dev/null +++ b/examples/web_sys/js_callback/static/index.html @@ -0,0 +1,11 @@ + + + + + (Asynchronous) callback from JavaScript + + + + + + diff --git a/examples/web_sys/mount_point/Cargo.toml b/examples/web_sys/mount_point/Cargo.toml new file mode 100644 index 00000000000..83688ddc4b3 --- /dev/null +++ b/examples/web_sys/mount_point/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "mount_point_web_sys" +version = "0.1.0" +authors = ["Ben Berman "] +edition = "2018" + +[dependencies] +wasm-bindgen = "0.2" +yew = { path = "../../..", features = ["web_sys"] } + +[dependencies.web-sys] +version = "0.3" +features = [ + "CanvasRenderingContext2d", + "Document", + "DomTokenList", + "Element", + "HtmlCanvasElement", + "Node", + "Window" +] diff --git a/examples/web_sys/mount_point/src/lib.rs b/examples/web_sys/mount_point/src/lib.rs new file mode 100644 index 00000000000..29c728b1ba3 --- /dev/null +++ b/examples/web_sys/mount_point/src/lib.rs @@ -0,0 +1,42 @@ +use yew::{html, Component, ComponentLink, Html, InputData, ShouldRender}; + +pub struct Model { + link: ComponentLink, + name: String, +} + +pub enum Msg { + UpdateName(String), +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { + link, + name: "Reversed".to_owned(), + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::UpdateName(new_name) => { + self.name = new_name; + } + } + true + } + + fn view(&self) -> Html { + html! { +
    + +

    { self.name.chars().rev().collect::() }

    +
    + } + } +} diff --git a/examples/web_sys/mount_point/src/main.rs b/examples/web_sys/mount_point/src/main.rs new file mode 100644 index 00000000000..484d88d4403 --- /dev/null +++ b/examples/web_sys/mount_point/src/main.rs @@ -0,0 +1,31 @@ +use mount_point_web_sys::Model; +use wasm_bindgen::JsValue; +use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; +use yew::App; + +fn main() { + yew::initialize(); + let document = yew::utils::document(); + let body = document.query_selector("body").unwrap().unwrap(); + + // This canvas won't be overwritten by yew! + let canvas = document.create_element("canvas").unwrap(); + body.append_child(&canvas).unwrap(); + + let canvas = HtmlCanvasElement::from(JsValue::from(canvas)); + canvas.set_width(100); + canvas.set_height(100); + let ctx = + CanvasRenderingContext2d::from(JsValue::from(canvas.get_context("2d").unwrap().unwrap())); + ctx.set_fill_style(&JsValue::from_str("green")); + ctx.fill_rect(10., 10., 50., 50.); + + let mount_class = "mount-point"; + let mount_point = document.create_element("div").unwrap(); + let class_list = mount_point.class_list(); + class_list.add_1(mount_class).unwrap(); + body.append_child(&mount_point).unwrap(); + + App::::new().mount(mount_point); + yew::run_loop(); +} diff --git a/examples/web_sys/multi_thread/Cargo.toml b/examples/web_sys/multi_thread/Cargo.toml new file mode 100644 index 00000000000..37fe4483317 --- /dev/null +++ b/examples/web_sys/multi_thread/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "multi_thread_web_sys" +version = "0.1.0" +authors = ["Denis Kolodin "] +edition = "2018" + +[[bin]] +name = "main" +path = "src/bin/main.rs" + +[[bin]] +name = "native_worker" +path = "src/bin/native_worker.rs" + +[dependencies] +log = "0.4" +wasm-logger = "0.2" +serde = "1.0" +serde_derive = "1.0" +yew = { path = "../../..", features = ["agent", "services", "web_sys"]} diff --git a/examples/web_sys/multi_thread/README.md b/examples/web_sys/multi_thread/README.md new file mode 100644 index 00000000000..efe100aad0d --- /dev/null +++ b/examples/web_sys/multi_thread/README.md @@ -0,0 +1,7 @@ +### multi_thread + +You should compile a worker which have to be spawned in a separate thread: + +```sh +wasm-pack build --target no-modules --release -- --features web_sys --bin native_worker +``` diff --git a/examples/web_sys/multi_thread/Web.toml b/examples/web_sys/multi_thread/Web.toml new file mode 100644 index 00000000000..813e27393a9 --- /dev/null +++ b/examples/web_sys/multi_thread/Web.toml @@ -0,0 +1 @@ +default-target = "wasm32-unknown-unknown" diff --git a/examples/web_sys/multi_thread/src/bin/main.rs b/examples/web_sys/multi_thread/src/bin/main.rs new file mode 100644 index 00000000000..3cf99c96aeb --- /dev/null +++ b/examples/web_sys/multi_thread/src/bin/main.rs @@ -0,0 +1,4 @@ +fn main() { + wasm_logger::init(wasm_logger::Config::default()); + yew::start_app::(); +} diff --git a/examples/web_sys/multi_thread/src/bin/native_worker.rs b/examples/web_sys/multi_thread/src/bin/native_worker.rs new file mode 100644 index 00000000000..80f6ddf266e --- /dev/null +++ b/examples/web_sys/multi_thread/src/bin/native_worker.rs @@ -0,0 +1,8 @@ +use yew::agent::Threaded; + +fn main() { + wasm_logger::init(wasm_logger::Config::default()); + yew::initialize(); + multi_thread_web_sys::native_worker::Worker::register(); + yew::run_loop(); +} diff --git a/examples/web_sys/multi_thread/src/context.rs b/examples/web_sys/multi_thread/src/context.rs new file mode 100644 index 00000000000..c93a9461cb8 --- /dev/null +++ b/examples/web_sys/multi_thread/src/context.rs @@ -0,0 +1,66 @@ +use log::info; +use serde_derive::{Deserialize, Serialize}; +use std::time::Duration; +use yew::worker::*; +// TODO use yew::services::{IntervalService, FetchService, Task}; +use yew::services::fetch::FetchService; +use yew::services::interval::IntervalService; +use yew::services::Task; + +#[derive(Serialize, Deserialize, Debug)] +pub enum Request { + GetDataFromServer, +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum Response { + DataFetched, +} + +pub enum Msg { + Updating, +} + +pub struct Worker { + link: AgentLink, + interval: IntervalService, + task: Box, + fetch: FetchService, +} + +impl Agent for Worker { + type Reach = Context; + type Message = Msg; + type Input = Request; + type Output = Response; + + fn create(link: AgentLink) -> Self { + let mut interval = IntervalService::new(); + let duration = Duration::from_secs(3); + let callback = link.callback(|_| Msg::Updating); + let task = interval.spawn(duration, callback); + Worker { + link, + interval, + task: Box::new(task), + fetch: FetchService::new(), + } + } + + fn update(&mut self, msg: Self::Message) { + match msg { + Msg::Updating => { + info!("Tick..."); + } + } + } + + fn handle_input(&mut self, msg: Self::Input, who: HandlerId) { + info!("Request: {:?}", msg); + match msg { + Request::GetDataFromServer => { + self.link.respond(who, Response::DataFetched); + } + } + } +} diff --git a/examples/web_sys/multi_thread/src/job.rs b/examples/web_sys/multi_thread/src/job.rs new file mode 100644 index 00000000000..a9d21aa4144 --- /dev/null +++ b/examples/web_sys/multi_thread/src/job.rs @@ -0,0 +1,66 @@ +use log::info; +use serde_derive::{Deserialize, Serialize}; +use std::time::Duration; +use yew::worker::*; +// TODO use yew::services::{IntervalService, FetchService, Task}; +use yew::services::fetch::FetchService; +use yew::services::interval::IntervalService; +use yew::services::Task; + +#[derive(Serialize, Deserialize, Debug)] +pub enum Request { + GetDataFromServer, +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum Response { + DataFetched, +} + +pub enum Msg { + Updating, +} + +pub struct Worker { + link: AgentLink, + interval: IntervalService, + task: Box, + fetch: FetchService, +} + +impl Agent for Worker { + type Reach = Job; + type Message = Msg; + type Input = Request; + type Output = Response; + + fn create(link: AgentLink) -> Self { + let mut interval = IntervalService::new(); + let duration = Duration::from_secs(3); + let callback = link.callback(|_| Msg::Updating); + let task = interval.spawn(duration, callback); + Worker { + link, + interval, + task: Box::new(task), + fetch: FetchService::new(), + } + } + + fn update(&mut self, msg: Self::Message) { + match msg { + Msg::Updating => { + info!("Tick..."); + } + } + } + + fn handle_input(&mut self, msg: Self::Input, who: HandlerId) { + info!("Request: {:?}", msg); + match msg { + Request::GetDataFromServer => { + self.link.respond(who, Response::DataFetched); + } + } + } +} diff --git a/examples/web_sys/multi_thread/src/lib.rs b/examples/web_sys/multi_thread/src/lib.rs new file mode 100644 index 00000000000..e86645d0332 --- /dev/null +++ b/examples/web_sys/multi_thread/src/lib.rs @@ -0,0 +1,82 @@ +#![recursion_limit = "128"] + +pub mod context; +pub mod job; +pub mod native_worker; + +use log::info; +use yew::worker::*; +use yew::{html, Component, ComponentLink, Html, ShouldRender}; + +pub struct Model { + link: ComponentLink, + worker: Box>, + job: Box>, + context: Box>, + context_2: Box>, +} + +pub enum Msg { + SendToWorker, + SendToJob, + SendToContext, + DataReceived, +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + let callback = link.callback(|_| Msg::DataReceived); + let worker = native_worker::Worker::bridge(callback); + + let callback = link.callback(|_| Msg::DataReceived); + let job = job::Worker::bridge(callback); + + let callback = link.callback(|_| Msg::DataReceived); + let context = context::Worker::bridge(callback); + + let callback = link.callback(|_| Msg::DataReceived); + let context_2 = context::Worker::bridge(callback); + + Model { + link, + worker, + job, + context, + context_2, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::SendToWorker => { + self.worker.send(native_worker::Request::GetDataFromServer); + } + Msg::SendToJob => { + self.job.send(job::Request::GetDataFromServer); + } + Msg::SendToContext => { + self.context.send(context::Request::GetDataFromServer); + self.context_2.send(context::Request::GetDataFromServer); + } + Msg::DataReceived => { + info!("DataReceived"); + } + } + true + } + + fn view(&self) -> Html { + html! { +
    + +
    + } + } +} diff --git a/examples/web_sys/multi_thread/src/native_worker.rs b/examples/web_sys/multi_thread/src/native_worker.rs new file mode 100644 index 00000000000..723d54cec46 --- /dev/null +++ b/examples/web_sys/multi_thread/src/native_worker.rs @@ -0,0 +1,70 @@ +use log::info; +use serde_derive::{Deserialize, Serialize}; +use std::time::Duration; +use yew::worker::*; +// TODO use yew::services::{IntervalService, FetchService, Task}; +use yew::services::fetch::FetchService; +use yew::services::interval::IntervalService; +use yew::services::Task; + +#[derive(Serialize, Deserialize, Debug)] +pub enum Request { + GetDataFromServer, +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum Response { + DataFetched, +} + +pub enum Msg { + Updating, +} + +pub struct Worker { + link: AgentLink, + interval: IntervalService, + task: Box, + fetch: FetchService, +} + +impl Agent for Worker { + type Reach = Public; + type Message = Msg; + type Input = Request; + type Output = Response; + + fn create(link: AgentLink) -> Self { + let mut interval = IntervalService::new(); + let duration = Duration::from_secs(3); + let callback = link.callback(|_| Msg::Updating); + let task = interval.spawn(duration, callback); + Worker { + link, + interval, + task: Box::new(task), + fetch: FetchService::new(), + } + } + + fn update(&mut self, msg: Self::Message) { + match msg { + Msg::Updating => { + info!("Tick..."); + } + } + } + + fn handle_input(&mut self, msg: Self::Input, who: HandlerId) { + info!("Request: {:?}", msg); + match msg { + Request::GetDataFromServer => { + self.link.respond(who, Response::DataFetched); + } + } + } + + fn name_of_resource() -> &'static str { + "bin/native_worker.js" + } +} diff --git a/examples/web_sys/multi_thread/static/bin b/examples/web_sys/multi_thread/static/bin new file mode 100644 index 00000000000..4a2105e009b --- /dev/null +++ b/examples/web_sys/multi_thread/static/bin @@ -0,0 +1 @@ +../../../target/wasm32-unknown-unknown/release \ No newline at end of file diff --git a/examples/web_sys/multi_thread/static/index.html b/examples/web_sys/multi_thread/static/index.html new file mode 100644 index 00000000000..11845751fc2 --- /dev/null +++ b/examples/web_sys/multi_thread/static/index.html @@ -0,0 +1,11 @@ + + + + + Yew • Multi-Thread + + + + + + diff --git a/examples/web_sys/node_refs/Cargo.toml b/examples/web_sys/node_refs/Cargo.toml new file mode 100644 index 00000000000..e6617be1864 --- /dev/null +++ b/examples/web_sys/node_refs/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "node_refs_web_sys" +version = "0.1.0" +authors = ["Justin Starry "] +edition = "2018" + +[dependencies] +yew = { path = "../../..", features = ["web_sys"] } +web-sys = { version = "0.3", features = ["HtmlElement", "HtmlInputElement", "Node"] } diff --git a/examples/web_sys/node_refs/src/input.rs b/examples/web_sys/node_refs/src/input.rs new file mode 100644 index 00000000000..e26f27c4ad4 --- /dev/null +++ b/examples/web_sys/node_refs/src/input.rs @@ -0,0 +1,43 @@ +use yew::prelude::*; + +pub struct InputComponent { + props: Props, + link: ComponentLink, +} + +#[derive(Clone, Properties)] +pub struct Props { + #[props(required)] + pub on_hover: Callback<()>, +} + +pub enum Msg { + Hover, +} + +impl Component for InputComponent { + type Message = Msg; + type Properties = Props; + + fn create(props: Self::Properties, link: ComponentLink) -> Self { + InputComponent { props, link } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::Hover => { + self.props.on_hover.emit(()); + } + } + false + } + + fn view(&self) -> Html { + html! { + + } + } +} diff --git a/examples/web_sys/node_refs/src/lib.rs b/examples/web_sys/node_refs/src/lib.rs new file mode 100644 index 00000000000..63312b715b4 --- /dev/null +++ b/examples/web_sys/node_refs/src/lib.rs @@ -0,0 +1,74 @@ +#![recursion_limit = "256"] + +mod input; + +use input::InputComponent; +use web_sys::HtmlInputElement as InputElement; +use yew::prelude::*; + +pub struct Model { + link: ComponentLink, + refs: Vec, + focus_index: usize, +} + +pub enum Msg { + HoverIndex(usize), +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { + link, + focus_index: 0, + refs: vec![NodeRef::default(), NodeRef::default()], + } + } + + fn mounted(&mut self) -> ShouldRender { + if let Some(input) = self.refs[self.focus_index].cast::() { + input.focus().unwrap(); + } + false + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::HoverIndex(index) => self.focus_index = index, + } + if let Some(input) = self.refs[self.focus_index].cast::() { + input.focus().unwrap(); + } + true + } + + fn view(&self) -> Html { + html! { +
    +

    { "Node Refs Demo" }

    +

    { "Refs can be used to access and manipulate DOM elements directly" }

    +
      +
    • { "First input will focus on mount" }
    • +
    • { "Each input will focus on hover" }
    • +
    +
    + + +
    +
    + + +
    +
    + } + } +} diff --git a/examples/web_sys/node_refs/src/main.rs b/examples/web_sys/node_refs/src/main.rs new file mode 100644 index 00000000000..f771d6d5dad --- /dev/null +++ b/examples/web_sys/node_refs/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/web_sys/npm_and_rest/Cargo.toml b/examples/web_sys/npm_and_rest/Cargo.toml new file mode 100644 index 00000000000..7b44cfe7131 --- /dev/null +++ b/examples/web_sys/npm_and_rest/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "npm_and_rest_web_sys" +version = "0.1.0" +authors = ["Denis Kolodin "] +edition = "2018" + +[dependencies] +anyhow = "1" +js-sys = "0.3" +serde = "1" +serde_derive = "1" +wasm-bindgen = "0.2" +web-sys = { version = "0.3", features = ["console"] } +yew = { path = "../../..", features = ["web_sys"] } diff --git a/examples/web_sys/npm_and_rest/src/ccxt.rs b/examples/web_sys/npm_and_rest/src/ccxt.rs new file mode 100644 index 00000000000..ae3f9de2b32 --- /dev/null +++ b/examples/web_sys/npm_and_rest/src/ccxt.rs @@ -0,0 +1,34 @@ +use js_sys::{Array, Reflect}; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; +use web_sys::console; + +#[derive(Default)] +pub struct CcxtService(Option<&'static JsValue>); + +#[wasm_bindgen] +extern "C" { + static ccxt: JsValue; +} + +impl CcxtService { + pub fn new() -> Self { + let lib: &JsValue = &ccxt; + CcxtService(Some(lib)) + } + + pub fn exchanges(&mut self) -> Vec { + let lib = self.0.as_ref().expect("ccxt library object lost"); + let v = { + let exchanges = Reflect::get(lib, &JsValue::from_str("exchanges")).unwrap(); + console::log_1(&exchanges); + exchanges + }; + let v: Vec = Array::from(&v) + .to_vec() + .into_iter() + .map(|v| v.as_string().expect("can't extract exchanges")) + .collect(); + v + } +} diff --git a/examples/web_sys/npm_and_rest/src/gravatar.rs b/examples/web_sys/npm_and_rest/src/gravatar.rs new file mode 100644 index 00000000000..845a9d71785 --- /dev/null +++ b/examples/web_sys/npm_and_rest/src/gravatar.rs @@ -0,0 +1,51 @@ +use anyhow::{anyhow, Error}; +use serde_derive::Deserialize; +use yew::callback::Callback; +use yew::format::{Json, Nothing}; +use yew::services::fetch::{FetchService, FetchTask, Request, Response}; + +#[derive(Deserialize, Debug)] +pub struct Profile { + entry: Vec, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Entry { + id: String, + hash: String, + request_hash: String, + profile_url: String, + preferred_username: String, +} + +#[derive(Default)] +pub struct GravatarService { + web: FetchService, +} + +impl GravatarService { + pub fn new() -> Self { + Self { + web: FetchService::new(), + } + } + + pub fn profile(&mut self, hash: &str, callback: Callback>) -> FetchTask { + let url = format!("https://en.gravatar.com/{}.json", hash); + let handler = move |response: Response>>| { + let (meta, Json(data)) = response.into_parts(); + if meta.status.is_success() { + callback.emit(data) + } else { + // format_err! is a macro in crate `failure` + callback.emit(Err(anyhow!( + "{}: error getting profile https://gravatar.com/", + meta.status + ))) + } + }; + let request = Request::get(url.as_str()).body(Nothing).unwrap(); + self.web.fetch(request, handler.into()).unwrap() + } +} diff --git a/examples/web_sys/npm_and_rest/src/lib.rs b/examples/web_sys/npm_and_rest/src/lib.rs new file mode 100644 index 00000000000..97b6a16c116 --- /dev/null +++ b/examples/web_sys/npm_and_rest/src/lib.rs @@ -0,0 +1,83 @@ +#![recursion_limit = "128"] + +// Own services implementation +pub mod ccxt; +pub mod gravatar; + +use anyhow::Error; +use yew::services::fetch::FetchTask; +use yew::{html, Callback, Component, ComponentLink, Html, ShouldRender}; + +use ccxt::CcxtService; +use gravatar::{GravatarService, Profile}; + +pub struct Model { + link: ComponentLink, + gravatar: GravatarService, + ccxt: CcxtService, + callback: Callback>, + profile: Option, + exchanges: Vec, + task: Option, +} + +pub enum Msg { + Gravatar, + GravatarReady(Result), + Exchanges, +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { + link: link.clone(), + gravatar: GravatarService::new(), + ccxt: CcxtService::new(), + callback: link.callback(Msg::GravatarReady), + profile: None, + exchanges: Vec::new(), + task: None, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::Gravatar => { + let task = self + .gravatar + .profile("205e460b479e2e5b48aec07710c08d50", self.callback.clone()); + self.task = Some(task); + } + Msg::GravatarReady(Ok(profile)) => { + self.profile = Some(profile); + } + Msg::GravatarReady(Err(_)) => { + // Can't load gravatar profile + } + Msg::Exchanges => { + self.exchanges = self.ccxt.exchanges(); + } + } + true + } + + fn view(&self) -> Html { + let view_exchange = |exchange| { + html! { +
  • { exchange }
  • + } + }; + html! { +
    + + +
      + { for self.exchanges.iter().map(view_exchange) } +
    +
    + } + } +} diff --git a/examples/web_sys/npm_and_rest/src/main.rs b/examples/web_sys/npm_and_rest/src/main.rs new file mode 100644 index 00000000000..d9c7a9efb69 --- /dev/null +++ b/examples/web_sys/npm_and_rest/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/web_sys/npm_and_rest/static/index.html b/examples/web_sys/npm_and_rest/static/index.html new file mode 100644 index 00000000000..a0e34651c2b --- /dev/null +++ b/examples/web_sys/npm_and_rest/static/index.html @@ -0,0 +1,12 @@ + + + + + Yew • npm and REST + + + + + + + diff --git a/examples/web_sys/todomvc/Cargo.toml b/examples/web_sys/todomvc/Cargo.toml new file mode 100644 index 00000000000..0af8a7727ba --- /dev/null +++ b/examples/web_sys/todomvc/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "todomvc_web_sys" +version = "0.1.0" +authors = ["Denis Kolodin "] +edition = "2018" + +[dependencies] +strum = "0.13" +strum_macros = "0.13" +serde = "1" +serde_derive = "1" +yew = { path = "../../..", features = ["web_sys"] } diff --git a/examples/web_sys/todomvc/README.md b/examples/web_sys/todomvc/README.md new file mode 100644 index 00000000000..5249541a236 --- /dev/null +++ b/examples/web_sys/todomvc/README.md @@ -0,0 +1,6 @@ +## Yew TodoMVC Demo + +This it an implementationt of [TodoMVC](http://todomvc.com/) app. + +Unlike other implementations, this stores the full state of the model, +including: all entries, entered text and chosen filter. diff --git a/examples/web_sys/todomvc/src/lib.rs b/examples/web_sys/todomvc/src/lib.rs new file mode 100644 index 00000000000..fb0760ab248 --- /dev/null +++ b/examples/web_sys/todomvc/src/lib.rs @@ -0,0 +1,358 @@ +#![recursion_limit = "512"] + +use serde_derive::{Deserialize, Serialize}; +use strum::IntoEnumIterator; +use strum_macros::{EnumIter, ToString}; +use yew::events::KeyboardEvent; +use yew::format::Json; +use yew::services::storage::{Area, StorageService}; +use yew::{html, Component, ComponentLink, Href, Html, InputData, ShouldRender}; + +const KEY: &'static str = "yew.todomvc.self"; + +pub struct Model { + link: ComponentLink, + storage: StorageService, + state: State, +} + +#[derive(Serialize, Deserialize)] +pub struct State { + entries: Vec, + filter: Filter, + value: String, + edit_value: String, +} + +#[derive(Serialize, Deserialize)] +struct Entry { + description: String, + completed: bool, + editing: bool, +} + +pub enum Msg { + Add, + Edit(usize), + Update(String), + UpdateEdit(String), + Remove(usize), + SetFilter(Filter), + ToggleAll, + ToggleEdit(usize), + Toggle(usize), + ClearCompleted, + Nope, +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + let storage = StorageService::new(Area::Local).expect("storage was disabled by the user"); + let entries = { + if let Json(Ok(restored_model)) = storage.restore(KEY) { + restored_model + } else { + Vec::new() + } + }; + let state = State { + entries, + filter: Filter::All, + value: "".into(), + edit_value: "".into(), + }; + Model { + link, + storage, + state, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::Add => { + let entry = Entry { + description: self.state.value.clone(), + completed: false, + editing: false, + }; + self.state.entries.push(entry); + self.state.value = "".to_string(); + } + Msg::Edit(idx) => { + let edit_value = self.state.edit_value.clone(); + self.state.complete_edit(idx, edit_value); + self.state.edit_value = "".to_string(); + } + Msg::Update(val) => { + println!("Input: {}", val); + self.state.value = val; + } + Msg::UpdateEdit(val) => { + println!("Input: {}", val); + self.state.edit_value = val; + } + Msg::Remove(idx) => { + self.state.remove(idx); + } + Msg::SetFilter(filter) => { + self.state.filter = filter; + } + Msg::ToggleEdit(idx) => { + self.state.edit_value = self.state.entries[idx].description.clone(); + self.state.toggle_edit(idx); + } + Msg::ToggleAll => { + let status = !self.state.is_all_completed(); + self.state.toggle_all(status); + } + Msg::Toggle(idx) => { + self.state.toggle(idx); + } + Msg::ClearCompleted => { + self.state.clear_completed(); + } + Msg::Nope => {} + } + self.storage.store(KEY, Json(&self.state.entries)); + true + } + + fn view(&self) -> Html { + html! { +
    +
    +
    +

    { "todos" }

    + { self.view_input() } +
    +
    + +
      + { for self.state.entries.iter().filter(|e| self.state.filter.fit(e)).enumerate().map(|e| self.view_entry(e)) } +
    +
    +
    + + { self.state.total() } + { " item(s) left" } + +
      + { for Filter::iter().map(|flt| self.view_filter(flt)) } +
    + +
    +
    + +
    + } + } +} + +impl Model { + fn view_filter(&self, filter: Filter) -> Html { + let flt = filter.clone(); + html! { +
  • + + { filter } + +
  • + } + } + + fn view_input(&self) -> Html { + html! { + // You can use standard Rust comments. One line: + //
  • + + /* Or multiline: +
      +
    • +
    + */ + } + } + + fn view_entry(&self, (idx, entry): (usize, &Entry)) -> Html { + let mut class = "todo".to_string(); + if entry.editing { + class.push_str(" editing"); + } + if entry.completed { + class.push_str(" completed"); + } + html! { +
  • +
    + + +
    + { self.view_entry_edit_input((idx, &entry)) } +
  • + } + } + + fn view_entry_edit_input(&self, (idx, entry): (usize, &Entry)) -> Html { + if entry.editing { + html! { + + } + } else { + html! { } + } + } +} + +#[derive(EnumIter, ToString, Clone, PartialEq, Serialize, Deserialize)] +pub enum Filter { + All, + Active, + Completed, +} + +impl<'a> Into for &'a Filter { + fn into(self) -> Href { + match *self { + Filter::All => "#/".into(), + Filter::Active => "#/active".into(), + Filter::Completed => "#/completed".into(), + } + } +} + +impl Filter { + fn fit(&self, entry: &Entry) -> bool { + match *self { + Filter::All => true, + Filter::Active => !entry.completed, + Filter::Completed => entry.completed, + } + } +} + +impl State { + fn total(&self) -> usize { + self.entries.len() + } + + fn total_completed(&self) -> usize { + self.entries + .iter() + .filter(|e| Filter::Completed.fit(e)) + .count() + } + + fn is_all_completed(&self) -> bool { + let mut filtered_iter = self + .entries + .iter() + .filter(|e| self.filter.fit(e)) + .peekable(); + + if filtered_iter.peek().is_none() { + return false; + } + + filtered_iter.all(|e| e.completed) + } + + fn toggle_all(&mut self, value: bool) { + for entry in self.entries.iter_mut() { + if self.filter.fit(entry) { + entry.completed = value; + } + } + } + + fn clear_completed(&mut self) { + let entries = self + .entries + .drain(..) + .filter(|e| Filter::Active.fit(e)) + .collect(); + self.entries = entries; + } + + fn toggle(&mut self, idx: usize) { + let filter = self.filter.clone(); + let mut entries = self + .entries + .iter_mut() + .filter(|e| filter.fit(e)) + .collect::>(); + let entry = entries.get_mut(idx).unwrap(); + entry.completed = !entry.completed; + } + + fn toggle_edit(&mut self, idx: usize) { + let filter = self.filter.clone(); + let mut entries = self + .entries + .iter_mut() + .filter(|e| filter.fit(e)) + .collect::>(); + let entry = entries.get_mut(idx).unwrap(); + entry.editing = !entry.editing; + } + + fn complete_edit(&mut self, idx: usize, val: String) { + let filter = self.filter.clone(); + let mut entries = self + .entries + .iter_mut() + .filter(|e| filter.fit(e)) + .collect::>(); + let entry = entries.get_mut(idx).unwrap(); + entry.description = val; + entry.editing = !entry.editing; + } + + fn remove(&mut self, idx: usize) { + let idx = { + let filter = self.filter.clone(); + let entries = self + .entries + .iter() + .enumerate() + .filter(|&(_, e)| filter.fit(e)) + .collect::>(); + let &(idx, _) = entries.get(idx).unwrap(); + idx + }; + self.entries.remove(idx); + } +} diff --git a/examples/web_sys/todomvc/src/main.rs b/examples/web_sys/todomvc/src/main.rs new file mode 100644 index 00000000000..ba7deffbd24 --- /dev/null +++ b/examples/web_sys/todomvc/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + yew::start_app::(); +} diff --git a/examples/web_sys/todomvc/static/index.html b/examples/web_sys/todomvc/static/index.html new file mode 100644 index 00000000000..4f2cf27d37f --- /dev/null +++ b/examples/web_sys/todomvc/static/index.html @@ -0,0 +1,13 @@ + + + + + Yew • TodoMVC + + + + + + + + diff --git a/examples/npm_and_rest/Cargo.toml b/examples/web_sys/two_apps/Cargo.toml similarity index 59% rename from examples/npm_and_rest/Cargo.toml rename to examples/web_sys/two_apps/Cargo.toml index 146b3b7a753..36ff90bd306 100644 --- a/examples/npm_and_rest/Cargo.toml +++ b/examples/web_sys/two_apps/Cargo.toml @@ -1,12 +1,9 @@ [package] -name = "npm_and_rest" +name = "two_apps_web_sys" version = "0.1.0" authors = ["Denis Kolodin "] edition = "2018" [dependencies] -anyhow = "1" -serde = "1" -serde_derive = "1" stdweb = "0.4.20" -yew = { path = "../.." } +yew = { path = "../../..", features = ["web_sys"] } diff --git a/examples/web_sys/two_apps/src/lib.rs b/examples/web_sys/two_apps/src/lib.rs new file mode 100644 index 00000000000..232ad785fd4 --- /dev/null +++ b/examples/web_sys/two_apps/src/lib.rs @@ -0,0 +1,83 @@ +#![recursion_limit = "256"] + +use yew::html::Scope; +/// This example demonstrates low-level usage of scopes. +use yew::{html, Component, ComponentLink, Html, ShouldRender}; + +pub struct Model { + link: ComponentLink, + scope: Option>, + selector: &'static str, + title: String, +} + +pub enum Msg { + SetScope(Scope), + SendToOpposite(String), + SetTitle(String), +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { + link, + scope: None, + selector: "", + title: "Nothing".into(), + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::SetScope(scope) => { + self.scope = Some(scope); + } + Msg::SendToOpposite(title) => { + self.scope + .as_mut() + .unwrap() + .send_message(Msg::SetTitle(title)); + } + Msg::SetTitle(title) => { + match title.as_ref() { + "Ping" => { + self.scope + .as_mut() + .unwrap() + .send_message(Msg::SetTitle("Pong".into())); + } + "Pong" => { + self.scope + .as_mut() + .unwrap() + .send_message(Msg::SetTitle("Pong Done".into())); + } + "Pong Done" => { + self.scope + .as_mut() + .unwrap() + .send_message(Msg::SetTitle("Ping Done".into())); + } + _ => {} + } + self.title = title; + } + } + true + } + + fn view(&self) -> Html { + html! { +
    +

    { format!("{} received <{}>", self.selector, self.title) }

    + + + + +
    + } + } +} diff --git a/examples/two_apps/src/main.rs b/examples/web_sys/two_apps/src/main.rs similarity index 59% rename from examples/two_apps/src/main.rs rename to examples/web_sys/two_apps/src/main.rs index c306990c97b..a0f3cc8018c 100644 --- a/examples/two_apps/src/main.rs +++ b/examples/web_sys/two_apps/src/main.rs @@ -1,10 +1,10 @@ -use stdweb::web::{document, IParentNode}; -use two_apps::{Model, Msg}; +use two_apps_web_sys::{Model, Msg}; use yew::html::Scope; use yew::App; fn mount_app(selector: &'static str, app: App) -> Scope { - let element = document().query_selector(selector).unwrap().unwrap(); + let document = yew::utils::document(); + let element = document.query_selector(selector).unwrap().unwrap(); app.mount(element) } @@ -12,8 +12,8 @@ fn main() { yew::initialize(); let first_app = App::new(); let second_app = App::new(); - let mut to_first = mount_app(".first-app", first_app); - let mut to_second = mount_app(".second-app", second_app); + let to_first = mount_app(".first-app", first_app); + let to_second = mount_app(".second-app", second_app); to_first.send_message(Msg::SetScope(to_second.clone())); to_second.send_message(Msg::SetScope(to_first.clone())); yew::run_loop(); diff --git a/examples/web_sys/two_apps/static/index.html b/examples/web_sys/two_apps/static/index.html new file mode 100644 index 00000000000..c01b03cda9c --- /dev/null +++ b/examples/web_sys/two_apps/static/index.html @@ -0,0 +1,14 @@ + + + + + Yew • Two Apps + + + +
    +
    + + + + diff --git a/src/agent.rs b/src/agent.rs index 550c0b1d4d2..4bfc25e4f13 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -4,6 +4,8 @@ use crate::callback::Callback; use crate::scheduler::{scheduler, Runnable, Shared}; use anymap::{self, AnyMap}; use bincode; +use cfg_if::cfg_if; +use cfg_match::cfg_match; use log::warn; use serde::{Deserialize, Serialize}; use slab::Slab; @@ -14,9 +16,18 @@ use std::fmt; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::rc::Rc; -use stdweb::Value; -#[allow(unused_imports)] -use stdweb::{_js_impl, js}; +cfg_if! { + if #[cfg(feature = "std_web")] { + use stdweb::Value; + #[allow(unused_imports)] + use stdweb::{_js_impl, js}; + } else if #[cfg(feature = "web_sys")] { + use crate::utils; + use js_sys::{Array, Reflect, Uint8Array}; + use wasm_bindgen::{closure::Closure, JsCast, JsValue}; + use web_sys::{Blob, BlobPropertyBag, DedicatedWorkerGlobalScope, MessageEvent, Url, Worker, WorkerOptions}; + } +} /// Serializable messages to worker #[derive(Serialize, Deserialize, Debug)] @@ -162,21 +173,29 @@ where ToWorker::Destroy => { let upd = AgentLifecycleEvent::Destroy; scope.send(upd); - js! { - // Terminates web worker - self.close(); + // Terminates web worker + cfg_match! { + feature = "std_web" => js! { self.close(); }, + feature = "web_sys" => worker_self().close(), }; } } }; let loaded: FromWorker = FromWorker::WorkerLoaded; let loaded = loaded.pack(); - js! { - var handler = @{handler}; - self.onmessage = function(event) { - handler(event.data); - }; - self.postMessage(@{loaded}); + cfg_match! { + feature = "std_web" => js! { + var handler = @{handler}; + self.onmessage = function(event) { + handler(event.data); + }; + self.postMessage(@{loaded}); + }, + feature = "web_sys" => ({ + let worker = worker_self(); + worker.set_onmessage_closure(handler); + worker.post_message_vec(loaded); + }), }; } } @@ -433,13 +452,20 @@ impl Discoverer for Private { }; // TODO(#947): Need somethig better... let name_of_resource = AGN::name_of_resource(); - let worker = js! { - var worker = new Worker(@{name_of_resource}); - var handler = @{handler}; - worker.onmessage = function(event) { - handler(event.data); - }; - return worker; + let worker = cfg_match! { + feature = "std_web" => js! { + var worker = new Worker(@{name_of_resource}); + var handler = @{handler}; + worker.onmessage = function(event) { + handler(event.data); + }; + return worker; + }, + feature = "web_sys" => ({ + let worker = worker_new(name_of_resource, AGN::is_module()); + worker.set_onmessage_closure(handler); + worker + }), }; let bridge = PrivateBridge { worker, @@ -451,7 +477,10 @@ impl Discoverer for Private { /// A connection manager for components interaction with workers. pub struct PrivateBridge { + #[cfg(feature = "std_web")] worker: Value, + #[cfg(feature = "web_sys")] + worker: Worker, _agent: PhantomData, } @@ -467,12 +496,17 @@ impl Bridge for PrivateBridge { // Use a queue to collect a messages if an instance is not ready // and send them to an agent when it will reported readiness. let msg = ToWorker::ProcessInput(SINGLETON_ID, msg).pack(); - let worker = &self.worker; - js! { - var worker = @{worker}; - var bytes = @{msg}; - worker.postMessage(bytes); - }; + cfg_match! { + feature = "std_web" => ({ + let worker = &self.worker; + js! { + var worker = @{worker}; + var bytes = @{msg}; + worker.postMessage(bytes); + }; + }), + feature = "web_sys" => self.worker.post_message_vec(msg), + } } } @@ -483,12 +517,19 @@ impl Drop for PrivateBridge { } struct RemoteAgent { + #[cfg(feature = "std_web")] worker: Value, + #[cfg(feature = "web_sys")] + worker: Worker, slab: SharedOutputSlab, } impl RemoteAgent { - pub fn new(worker: Value, slab: SharedOutputSlab) -> Self { + pub fn new( + #[cfg(feature = "std_web")] worker: Value, + #[cfg(feature = "web_sys")] worker: Worker, + slab: SharedOutputSlab, + ) -> Self { RemoteAgent { worker, slab } } @@ -533,7 +574,9 @@ impl Discoverer for Public { Rc::new(RefCell::new(Slab::new())); let handler = { let slab = slab.clone(); - move |data: Vec, worker: Value| { + move |data: Vec, + #[cfg(feature = "std_web")] worker: Value, + #[cfg(feature = "web_sys")] worker: &Worker| { let msg = FromWorker::::unpack(&data); match msg { FromWorker::WorkerLoaded => { @@ -546,8 +589,13 @@ impl Discoverer for Public { local.borrow_mut().get_mut(&TypeId::of::()) { for msg in msgs.drain(..) { - let worker = &worker; - js! {@{worker}.postMessage(@{msg});}; + cfg_match! { + feature = "std_web" => ({ + let worker = &worker; + js! {@{worker}.postMessage(@{msg});}; + }), + feature = "web_sys" => worker.post_message_vec(msg), + } } } }); @@ -559,13 +607,23 @@ impl Discoverer for Public { } }; let name_of_resource = AGN::name_of_resource(); - let worker = js! { - var worker = new Worker(@{name_of_resource}); - var handler = @{handler}; - worker.onmessage = function(event) { - handler(event.data, worker); - }; - return worker; + let worker = cfg_match! { + feature = "std_web" => js! { + var worker = new Worker(@{name_of_resource}); + var handler = @{handler}; + worker.onmessage = function(event) { + handler(event.data, worker); + }; + return worker; + }, + feature = "web_sys" => ({ + let worker = worker_new(name_of_resource, AGN::is_module()); + let worker_clone = worker.clone(); + worker.set_onmessage_closure(move |data: Vec| { + handler(data, &worker_clone); + }); + worker + }), }; let launched = RemoteAgent::new(worker, slab); entry.insert(launched).create_bridge(callback) @@ -580,7 +638,10 @@ impl Dispatchable for Public {} /// A connection manager for components interaction with workers. pub struct PublicBridge { + #[cfg(feature = "std_web")] worker: Value, + #[cfg(feature = "web_sys")] + worker: Worker, id: HandlerId, _agent: PhantomData, } @@ -614,15 +675,22 @@ impl PublicBridge { } } -fn send_to_remote(worker: &Value, msg: ToWorker) { +fn send_to_remote( + #[cfg(feature = "std_web")] worker: &Value, + #[cfg(feature = "web_sys")] worker: &Worker, + msg: ToWorker, +) { // TODO(#937): Important! Implement. // Use a queue to collect a messages if an instance is not ready // and send them to an agent when it will reported readiness. let msg = msg.pack(); - js! { - var worker = @{worker}; - var bytes = @{msg}; - worker.postMessage(bytes); + cfg_match! { + feature = "std_web" => js! { + var worker = @{worker}; + var bytes = @{msg}; + worker.postMessage(bytes); + }, + feature = "web_sys" => worker.post_message_vec(msg), }; } @@ -705,6 +773,12 @@ pub trait Agent: Sized + 'static { fn name_of_resource() -> &'static str { "main.js" } + + /// Signifies if resource is a module. + /// This has pending browser support. + fn is_module() -> bool { + false + } } /// This struct holds a reference to a component and to a global scheduler. @@ -761,9 +835,12 @@ impl Responder for WorkerResponder { fn respond(&self, id: HandlerId, output: AGN::Output) { let msg = FromWorker::ProcessOutput(id, output); let data = msg.pack(); - js! { - var data = @{data}; - self.postMessage(data); + cfg_match! { + feature = "std_web" => js! { + var data = @{data}; + self.postMessage(data); + }, + feature = "web_sys" => worker_self().post_message_vec(data), }; } } @@ -904,3 +981,75 @@ where } } } + +#[cfg(feature = "web_sys")] +fn worker_new(name_of_resource: &str, is_module: bool) -> Worker { + let href = utils::document().location().unwrap().href().unwrap(); + + let array = Array::new(); + array.push( + &format!( + "importScripts(\"{}{}\");onmessage=e=>{{wasm_bindgen(e.data)}}", + href, name_of_resource, + ) + .into(), + ); + let blob = Blob::new_with_str_sequence_and_options( + &array, + BlobPropertyBag::new().type_("application/javascript"), + ) + .unwrap(); + let url = Url::create_object_url_with_blob(&blob).unwrap(); + + if is_module { + let options = WorkerOptions::new(); + Reflect::set( + options.as_ref(), + &JsValue::from_str("type"), + &JsValue::from_str("module"), + ) + .unwrap(); + Worker::new_with_options(&url, &options).expect("failed to spawn worker") + } else { + Worker::new(&url).expect("failed to spawn worker") + } +} + +#[cfg(feature = "web_sys")] +fn worker_self() -> DedicatedWorkerGlobalScope { + JsValue::from(js_sys::global()).into() +} + +#[cfg(feature = "web_sys")] +trait WorkerExt { + fn set_onmessage_closure(&self, handler: impl 'static + Fn(Vec)); + + fn post_message_vec(&self, data: Vec); +} + +#[cfg(feature = "web_sys")] +macro_rules! worker_ext_impl { + ($($type:ident),+) => {$( + impl WorkerExt for $type { + fn set_onmessage_closure(&self, handler: impl 'static + Fn(Vec)) { + let handler = move |message: MessageEvent| { + let data = Uint8Array::from(message.data()).to_vec(); + handler(data); + }; + let closure = Closure::wrap(Box::new(handler) as Box); + self.set_onmessage(Some(closure.as_ref().unchecked_ref())); + closure.forget(); + } + + fn post_message_vec(&self, data: Vec) { + self.post_message(&Uint8Array::from(data.as_slice())) + .expect("failed to post message"); + } + } + )+}; +} + +#[cfg(feature = "web_sys")] +worker_ext_impl! { + Worker, DedicatedWorkerGlobalScope +} diff --git a/src/app.rs b/src/app.rs index 3e6a9c2835e..466e46a9050 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,15 @@ //! a component in an isolated scope. use crate::html::{Component, NodeRef, Scope}; -use stdweb::web::{document, Element, INode, IParentNode}; +use crate::utils::document; +use cfg_if::cfg_if; +cfg_if! { + if #[cfg(feature = "std_web")] { + use stdweb::web::{Element, INode, IParentNode}; + } else if #[cfg(feature = "web_sys")] { + use web_sys::Element; + } +} /// An application instance. #[derive(Debug)] diff --git a/src/components/select.rs b/src/components/select.rs index aaedb30b646..5af9bce3675 100644 --- a/src/components/select.rs +++ b/src/components/select.rs @@ -35,6 +35,7 @@ use crate::callback::Callback; use crate::html::{ChangeData, Component, ComponentLink, Html, ShouldRender}; use crate::macros::{html, Properties}; +use cfg_match::cfg_match; /// `Select` component. #[derive(Debug)] @@ -123,7 +124,11 @@ where fn onchange(&self) -> Callback { self.link.callback(|event| match event { ChangeData::Select(elem) => { - let value = elem.selected_index().map(|x| x as usize); + let value = elem.selected_index(); + let value = cfg_match! { + feature = "std_web" => value.map(|x| x as usize), + feature = "web_sys" => Some(value as usize), + }; Msg::Selected(value) } _ => { diff --git a/src/format/macros.rs b/src/format/macros.rs index 07873b3cd2f..b74b524b504 100644 --- a/src/format/macros.rs +++ b/src/format/macros.rs @@ -1,7 +1,6 @@ //! Contains three macros for wrapping serde format. Collectively they //! allow you to define your own text and binary wrappers. -#[macro_export] /// This macro is used for a format that can be encoded as Text. It /// is used in conjunction with a type definition for a tuple struct /// with one (publically accessible) element of a generic type. Since @@ -18,6 +17,7 @@ /// text_format!(Json based on serde_json); /// binary_format!(Json based on serde_json); /// ``` +#[macro_export] macro_rules! text_format { ($type:ident based on $format:ident) => { impl<'a, T> Into<$crate::format::Text> for $type<&'a T> @@ -43,7 +43,6 @@ macro_rules! text_format { }; } -#[macro_export] /// This macro is used for a format that can be encoded as Binary. It /// is used in conjunction with a type definition for a tuple struct /// with one (publicly accessible) element of a generic type. Not @@ -103,6 +102,7 @@ macro_rules! text_format { /// text_format_is_an_error!(Bincode); /// # } /// ``` +#[macro_export] macro_rules! binary_format { ($type:ident based on $format:ident) => { binary_format!($type, $format::to_vec, $format::from_slice); @@ -131,7 +131,6 @@ macro_rules! binary_format { }; } -#[macro_export] /// This macro is used for a format that can be encoded as Binary but /// can't be encoded as Text. It is used in conjunction with a type /// definition for a tuple struct with one (publically accessible) @@ -150,7 +149,8 @@ macro_rules! binary_format { /// text_format_is_an_error!(MsgPack); /// # } /// ``` - +#[macro_export] +#[cfg(any(feature = "bincode", feature = "cbor", feature = "msgpack"))] macro_rules! text_format_is_an_error { ($type:ident) => { use $crate::{format::FormatError, text_format}; diff --git a/src/html/listener.rs b/src/html/listener.rs deleted file mode 100644 index a064669790a..00000000000 --- a/src/html/listener.rs +++ /dev/null @@ -1,170 +0,0 @@ -use crate::callback::Callback; -use crate::virtual_dom::Listener; -use stdweb::web::html_element::SelectElement; -#[allow(unused_imports)] -use stdweb::web::{EventListenerHandle, FileList, INode}; -#[allow(unused_imports)] -use stdweb::{_js_impl, js}; - -macro_rules! impl_action { - ($($action:ident($event:ident : $type:ident) -> $ret:ty => $convert:expr)*) => {$( - /// An abstract implementation of a listener. - pub mod $action { - use stdweb::web::{IEventTarget, Element}; - use stdweb::web::event::{IEvent, $type}; - use super::*; - - /// A wrapper for a callback which attaches event listeners to elements. - #[derive(Clone, Debug)] - pub struct Wrapper { - callback: Callback, - } - - impl Wrapper { - /// Create a wrapper for an event-typed callback - pub fn new(callback: Callback) -> Self { - Wrapper { callback } - } - } - - /// And event type which keeps the returned type. - pub type Event = $ret; - - impl Listener for Wrapper { - fn kind(&self) -> &'static str { - stringify!($action) - } - - fn attach(&self, element: &Element) -> EventListenerHandle { - let this = element.clone(); - let callback = self.callback.clone(); - let listener = move |event: $type| { - event.stop_propagation(); - callback.emit($convert(&this, event)); - }; - element.add_event_listener(listener) - } - } - } - )*}; -} - -// Inspired by: http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Events -impl_action! { - onclick(event: ClickEvent) -> ClickEvent => |_, event| { event } - ondoubleclick(event: DoubleClickEvent) -> DoubleClickEvent => |_, event| { event } - onkeypress(event: KeyPressEvent) -> KeyPressEvent => |_, event| { event } - onkeydown(event: KeyDownEvent) -> KeyDownEvent => |_, event| { event } - onkeyup(event: KeyUpEvent) -> KeyUpEvent => |_, event| { event } - onmousemove(event: MouseMoveEvent) -> MouseMoveEvent => |_, event| { event } - onmousedown(event: MouseDownEvent) -> MouseDownEvent => |_, event| { event } - onmouseup(event: MouseUpEvent) -> MouseUpEvent => |_, event| { event } - onmouseover(event: MouseOverEvent) -> MouseOverEvent => |_, event| { event } - onmouseout(event: MouseOutEvent) -> MouseOutEvent => |_, event| { event } - onmouseenter(event: MouseEnterEvent) -> MouseEnterEvent => |_, event| { event } - onmouseleave(event: MouseLeaveEvent) -> MouseLeaveEvent => |_, event| { event } - onmousewheel(event: MouseWheelEvent) -> MouseWheelEvent => |_, event| { event } - ongotpointercapture(event: GotPointerCaptureEvent) -> GotPointerCaptureEvent => |_, event| { event } - onlostpointercapture(event: LostPointerCaptureEvent) -> LostPointerCaptureEvent => |_, event| { event } - onpointercancel(event: PointerCancelEvent) -> PointerCancelEvent => |_, event| { event } - onpointerdown(event: PointerDownEvent) -> PointerDownEvent => |_, event| { event } - onpointerenter(event: PointerEnterEvent) -> PointerEnterEvent => |_, event| { event } - onpointerleave(event: PointerLeaveEvent) -> PointerLeaveEvent => |_, event| { event } - onpointermove(event: PointerMoveEvent) -> PointerMoveEvent => |_, event| { event } - onpointerout(event: PointerOutEvent) -> PointerOutEvent => |_, event| { event } - onpointerover(event: PointerOverEvent) -> PointerOverEvent => |_, event| { event } - onpointerup(event: PointerUpEvent) -> PointerUpEvent => |_, event| { event } - onscroll(event: ScrollEvent) -> ScrollEvent => |_, event| { event } - onblur(event: BlurEvent) -> BlurEvent => |_, event| { event } - onfocus(event: FocusEvent) -> FocusEvent => |_, event| { event } - onsubmit(event: SubmitEvent) -> SubmitEvent => |_, event| { event } - ondragstart(event: DragStartEvent) -> DragStartEvent => |_, event| { event } - ondrag(event: DragEvent) -> DragEvent => |_, event| { event } - ondragend(event: DragEndEvent) -> DragEndEvent => |_, event| { event } - ondragenter(event: DragEnterEvent) -> DragEnterEvent => |_, event| { event } - ondragleave(event: DragLeaveEvent) -> DragLeaveEvent => |_, event| { event } - ondragover(event: DragOverEvent) -> DragOverEvent => |_, event| { event } - ondragexit(event: DragExitEvent) -> DragExitEvent => |_, event| { event } - ondrop(event: DragDropEvent) -> DragDropEvent => |_, event| { event } - oncontextmenu(event: ContextMenuEvent) -> ContextMenuEvent => |_, event| { event } - oninput(event: InputEvent) -> InputData => |this: &Element, _| { - use stdweb::web::html_element::{InputElement, TextAreaElement}; - use stdweb::unstable::TryInto; - // Normally only InputElement or TextAreaElement can have an oninput event listener. In - // practice though any element with `contenteditable=true` may generate such events, - // therefore here we fall back to just returning the text content of the node. - // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event. - let v1 = this.clone().try_into().map(|input: InputElement| input.raw_value()).ok(); - let v2 = this.clone().try_into().map(|input: TextAreaElement| input.value()).ok(); - let v3 = this.text_content(); - let value = v1.or(v2).or(v3) - .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener"); - InputData { value } - } - onchange(event: ChangeEvent) -> ChangeData => |this: &Element, _| { - use stdweb::web::{FileList, IElement}; - use stdweb::web::html_element::{InputElement, TextAreaElement, SelectElement}; - use stdweb::unstable::TryInto; - match this.node_name().as_ref() { - "INPUT" => { - let input: InputElement = this.clone().try_into().unwrap(); - let is_file = input.get_attribute("type").map(|value| { - value.eq_ignore_ascii_case("file") - }) - .unwrap_or(false); - if is_file { - let files: FileList = js!( return @{input}.files; ) - .try_into() - .unwrap(); - ChangeData::Files(files) - } else { - ChangeData::Value(input.raw_value()) - } - } - "TEXTAREA" => { - let tae: TextAreaElement = this.clone().try_into().unwrap(); - ChangeData::Value(tae.value()) - } - "SELECT" => { - let se: SelectElement = this.clone().try_into().unwrap(); - ChangeData::Select(se) - } - _ => { - panic!("only an InputElement, TextAreaElement or SelectElement can have an onchange event listener"); - } - } - } - ontouchcancel(event: TouchCancel) -> TouchCancel => |_, event| { event } - ontouchend(event: TouchEnd) -> TouchEnd => |_, event| { event } - ontouchenter(event: TouchEnter) -> TouchEnter => |_, event| { event } - ontouchmove(event: TouchMove) -> TouchMove => |_, event| { event } - ontouchstart(event: TouchStart) -> TouchStart => |_, event| { event } -} - -/// A type representing data from `oninput` event. -#[derive(Debug)] -pub struct InputData { - /// Inserted characters. Contains value from - /// [InputEvent](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/data). - pub value: String, -} - -// There is no '.../Web/API/ChangeEvent/data' (for onchange) similar to -// https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/data (for oninput). -// ChangeData actually contains the value of the InputElement/TextAreaElement -// after `change` event occured or contains the SelectElement (see more at the -// variant ChangeData::Select) - -/// A type representing change of value(s) of an element after committed by user -/// ([onchange event](https://developer.mozilla.org/en-US/docs/Web/Events/change)). -#[derive(Debug)] -pub enum ChangeData { - /// Value of the element in cases of ``, `