Rip out stdweb (#1697)

* feat: Remove usage of stdweb

* Cleanup Cargo.toml

* yew-services fixes

* fix doc test

Co-authored-by: Justin Starry <justin.starry@icloud.com>
This commit is contained in:
Philip Peterson 2021-01-23 11:14:15 -05:00 committed by GitHub
parent 5adb142be5
commit 5fc4387dc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
78 changed files with 1190 additions and 4070 deletions

View File

@ -1,2 +1,2 @@
[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))']
[target.'cfg(target_arch = "wasm32")']
runner = 'wasm-bindgen-test-runner'

View File

@ -27,8 +27,7 @@ If applicable, add screenshots to help explain your problem.
- Yew version: [e.g. v0.17, `master`]
- Rust version: [e.g. 1.43.0, `nightly`]
- Target, if relevant: [e.g. `wasm32-unknown-emscripten`]
- Build tool, if relevant: [e.g. `wasm-pack`, `cargo-web`]
- Web library: [`stdweb` OR `web-sys`]
- Build tool, if relevant: [e.g. `wasm-pack`, `trunk`]
- OS, if relevant: [e.g. MacOS]
- Browser and version, if relevant: [e.g. Chrome v83]

View File

@ -24,7 +24,6 @@ jobs:
~/.cargo/registry
~/.cargo/git
target
yew-stdweb/target
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}
restore-keys: |
cargo-${{ runner.os }}-
@ -48,12 +47,6 @@ jobs:
cd packages/yew
cargo clippy --all-targets --features "cbor msgpack toml yaml" -- -D warnings
- name: Run clippy - yew-stdweb with all features
if: always()
run: |
cd packages/yew-stdweb
cargo clippy --all-targets --features "cbor msgpack toml yaml" -- -D warnings
check_examples:
name: Check Examples
runs-on: ubuntu-latest
@ -119,15 +112,6 @@ jobs:
cd packages/yew
cargo test --doc --features "doc_test wasm_test yaml msgpack cbor toml"
- name: Run doctest - yew-stdweb with features
run: |
# Sadly we can't run the tests on yew-stdweb as the snippets use `yew`, not `yew_stdweb` so the imports wouldn't work.
# To fix this we just run them on yew but with yew-stdweb's default features enabled.
cd packages/yew
cargo test --doc \
--no-default-features --features "agent std_web" \
--features "doc_test wasm_test yaml msgpack cbor toml"
integration_tests:
name: Integration Tests on ${{ matrix.toolchain }}
runs-on: ubuntu-latest
@ -165,7 +149,6 @@ jobs:
~/.cargo/registry
~/.cargo/git
target
yew-stdweb/target
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}
restore-keys: |
cargo-${{ runner.os }}-
@ -175,12 +158,6 @@ jobs:
cd packages/yew
wasm-pack test --chrome --firefox --headless -- --features "wasm_test"
- name: Run tests - yew-stdweb
if: matrix.toolchain != 'stable'
run: |
cd packages/yew-stdweb
wasm-pack test --chrome --firefox --headless -- --features "wasm_test"
- name: Run tests - yew-functional
run: |
cd packages/yew-functional

View File

@ -22,14 +22,6 @@ cargo make --list-all-steps
The most important tasks are outlined below.
#### stdweb
To run the examples in `./yew-stdweb`, you may wish to install [cargo-web](https://github.com/koute/cargo-web):
```bash
cargo install cargo-web
```
## Tests
To run all tests, use the following command:

View File

@ -29,7 +29,7 @@ run_task = { name = ["lint", "tests"], fork = true }
[tasks.lint]
category = "Checks"
description = "Check formatting and run Clippy"
run_task = { name = ["lint-flow", "lint-stdweb"], fork = true }
run_task = { name = ["lint-flow"], fork = true }
[tasks.tests]
category = "Testing"
@ -38,7 +38,6 @@ dependencies = ["tests-setup"]
env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"] }
run_task = { name = [
"test-flow",
"tests-stdweb",
"doc-test-flow",
], fork = true, cleanup_task = "tests-cleanup" }
@ -53,19 +52,6 @@ private = true
workspace = true
dependencies = ["core::check-format-flow", "core::clippy-flow"]
[tasks.lint-stdweb]
private = true
cwd = "packages/yew-stdweb"
command = "cargo"
args = [
"make",
"--loglevel",
"${CARGO_MAKE_LOG_LEVEL}",
"--profile",
"${CARGO_MAKE_PROFILE}",
"lint-flow",
]
[tasks.tests-setup]
private = true
script_runner = "@duckscript"

View File

@ -7,10 +7,8 @@
- [Project Setup](getting-started/project-setup.md)
- [Using trunk](getting-started/project-setup/using-trunk.md)
- [Using wasm-pack](getting-started/project-setup/using-wasm-pack.md)
- [Using cargo-web](getting-started/project-setup/using-cargo-web.md)
- [Starter Templates](getting-started/starter-templates.md)
- [Build a Sample App](getting-started/build-a-sample-app.md)
- [Choose web-sys or stdweb](getting-started/choose-web-library.md)
- [Learn through examples](getting-started/examples.md)
## Core Concepts <a id="concepts"></a>

View File

@ -74,8 +74,7 @@ is also a parameter called `first_render` which can be used to determine whether
being called on the first render, or instead a subsequent one.
```rust
use stdweb::web::html_element::InputElement;
use stdweb::web::IHtmlElement;
use web_sys::HtmlInputElement;
use yew::prelude::*;
pub struct MyComponent {
@ -93,7 +92,7 @@ impl Component for MyComponent {
fn rendered(&mut self, first_render: bool) {
if first_render {
if let Some(input) = self.node_ref.cast::<InputElement>() {
if let Some(input) = self.node_ref.cast::<HtmlInputElement>() {
input.focus();
}
}

View File

@ -148,108 +148,106 @@ impl Component for MyComponent {
## Event Types
In the following table `web-sys`'s event types should only be used if you're using `yew` with `web-sys`
(this is enabled by default). Use `stdweb`'s event types if you're using the `yew-stdweb` crate. See
[the documentation page about whether to choose `web-sys` or `stdweb`](https://yew.rs/docs/getting-started/choose-web-library) for more information.
:::tip
All the event types mentioned in the following table are re-exported under `yew::events`.
Using the types from `yew::events` makes it easier to ensure version compatibility than if you were to manually include `web-sys` or `stdweb` as dependencies in your crate because you won't end up using a version which conflicts with the version Yew specifies.
Using the types from `yew::events` makes it easier to ensure version compatibility than
if you were to manually include `web-sys` as a dependency in your crate because you won't
end up using a version which conflicts with the version that Yew specifies.
:::
| Event name | `web_sys` Event Type | `stdweb` Event Type |
| --------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `onabort` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResourceAbortEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResourceAbortEvent.html) |
| `onauxclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [AuxClickEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.AuxClickEvent.html) |
| `onblur` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | [BlurEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.BlurEvent.html) |
| `oncancel` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oncanplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oncanplaythrough` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onchange` | [ChangeData](https://docs.rs/yew/latest/yew/events/enum.ChangeData.html) | [ChangeData](https://docs.rs/yew-stdweb/latest/yew_stdweb/events/enum.ChangeData.html) |
| `onclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [ClickEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ClickEvent.html) |
| `onclose` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oncontextmenu` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [ContextMenuEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ContextMenuEvent.html) |
| `oncuechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `ondblclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [DoubleClickEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DoubleClickEvent.html) |
| `ondrag` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragEvent.html) |
| `ondragend` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragEndEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragEndEvent.html) |
| `ondragenter` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragEnterEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragEnterEvent.html) |
| `ondragexit` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragExitEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragExitEvent.html) |
| `ondragleave` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.htmk) | [DragLeaveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragLeaveEvent.html) |
| `ondragover` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragOverEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragOverEvent.html) |
| `ondragstart` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragStartEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragStartEvent.html) |
| `ondrop` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragDropEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragDropEvent.html) |
| `ondurationchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onemptied` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onended` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResourceErrorEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResourceErrorEvent.html) |
| `onfocus` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | [FocusEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.FocusEvent.html) |
| `onformdata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oninput` | [InputData](https://docs.rs/yew/latest/yew/events/struct.InputData.html) | [InputData](https://docs.rs/yew-stdweb/latest/yew_stdweb/events/struct.InputData.html) |
| `oninvalid` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onkeydown` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | [KeyDownEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.KeyDownEvent.html) |
| `onkeypress` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | [KeyPressEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.KeyPressEvent.html) |
| `onkeyup` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | [KeyUpEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.KeyUpEvent.html) |
| `onload` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResourceLoadEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResourceLoadEvent.html) |
| `onloadeddata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onloadedmetadata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onloadstart` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | [LoadStartEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.LoadStartEvent.html) |
| `onmousedown` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseDownEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseDownEvent.html) |
| `onmouseenter` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseEnterEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseEnterEvent.html) |
| `onmouseleave` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseLeaveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseLeaveEvent.html) |
| `onmousemove` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseMoveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseMoveEvent.html) |
| `onmouseout` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseOutEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseOutEvent.html) |
| `onmouseover` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseOverEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseOverEvent.html) |
| `onmouseup` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseUpEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseUpEvent.html) |
| `onpause` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onplaying` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onprogress` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | [ProgressEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ProgressEvent.html) |
| `onratechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onreset` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onresize` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResizeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResizeEvent.html) |
| `onscroll` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ScrollEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ScrollEvent.html) |
| `onsecuritypolicyviolation` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onseeked` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onseeking` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onselect` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onslotchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [SlotChangeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.SlotChangeEvent.html) |
| `onstalled` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onsubmit` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | [SubmitEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.SubmitEvent.html) |
| `onsuspend` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `ontimeupdate` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `ontoggle` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onvolumechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onwaiting` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onwheel` | [WheelEvent](https://docs.rs/web-sys/latest/web_sys/struct.WheelEvent.html) | [MouseWheelEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseWheelEvent.html) |
| `oncopy` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `oncut` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onpaste` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onanimationcancel` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
| `onanimationend` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
| `onanimationiteration` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
| `onanimationstart` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
| `ongotpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [GotPointerCaptureEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.GotPointerCaptureEvent.html) |
| `onloadend` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | [LoadEndEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.LoadEndEvent.html) |
| `onlostpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [LostPointerCaptureEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.LostPointerCaptureEvent.html) |
| `onpointercancel` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerCancelEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerCancelEvent.html) |
| `onpointerdown` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerDownEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerDownEvent.html) |
| `onpointerenter` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerEnterEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerEnterEvent.html) |
| `onpointerleave` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerLeaveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerLeaveEvent.html) |
| `onpointerlockchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [PointerLockChangeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerLockChangeEvent.html) |
| `onpointerlockerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [PointerLockErrorEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerLockErrorEvent.html) |
| `onpointermove` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerMoveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerMoveEvent.html) |
| `onpointerout` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerOutEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerOutEvent.html) |
| `onpointerover` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerOverEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerOverEvent.html) |
| `onpointerup` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerUpEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerUpEvent.html) |
| `onselectionchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [SelectionChangeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.SelectionChangeEvent.html) |
| `onselectstart` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `onshow` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
| `ontouchcancel` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchCancel](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchCancel.html) |
| `ontouchend` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchEnd](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchEnd.html) |
| `ontouchmove` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchMove](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchMove.html) |
| `ontouchstart` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchStart](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchStart.html) |
| `ontransitioncancel` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
| `ontransitionend` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
| `ontransitionrun` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
| `ontransitionstart` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
| Event name | `web_sys` Event Type |
| --------------------------- | ------------------------------------------------------------------------------------- |
| `onabort` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onauxclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
| `onblur` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) |
| `oncancel` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `oncanplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `oncanplaythrough` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onchange` | [ChangeData](https://docs.rs/yew/latest/yew/events/enum.ChangeData.html) |
| `onclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
| `onclose` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `oncontextmenu` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
| `oncuechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `ondblclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
| `ondrag` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
| `ondragend` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
| `ondragenter` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
| `ondragexit` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
| `ondragleave` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.htmk) |
| `ondragover` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
| `ondragstart` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
| `ondrop` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
| `ondurationchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onemptied` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onended` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onfocus` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) |
| `onformdata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `oninput` | [InputData](https://docs.rs/yew/latest/yew/events/struct.InputData.html) |
| `oninvalid` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onkeydown` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) |
| `onkeypress` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) |
| `onkeyup` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) |
| `onload` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onloadeddata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onloadedmetadata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onloadstart` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) |
| `onmousedown` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
| `onmouseenter` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
| `onmouseleave` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
| `onmousemove` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
| `onmouseout` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
| `onmouseover` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
| `onmouseup` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
| `onpause` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onplaying` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onprogress` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) |
| `onratechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onreset` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onresize` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onscroll` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onsecuritypolicyviolation` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onseeked` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onseeking` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onselect` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onslotchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onstalled` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onsubmit` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) |
| `onsuspend` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `ontimeupdate` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `ontoggle` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onvolumechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onwaiting` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onwheel` | [WheelEvent](https://docs.rs/web-sys/latest/web_sys/struct.WheelEvent.html) |
| `oncopy` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `oncut` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onpaste` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onanimationcancel` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) |
| `onanimationend` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) |
| `onanimationiteration` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) |
| `onanimationstart` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) |
| `ongotpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
| `onloadend` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) |
| `onlostpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
| `onpointercancel` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
| `onpointerdown` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
| `onpointerenter` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
| `onpointerleave` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
| `onpointerlockchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onpointerlockerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onpointermove` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
| `onpointerout` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
| `onpointerover` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
| `onpointerup` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
| `onselectionchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onselectstart` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `onshow` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
| `ontouchcancel` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) |
| `ontouchend` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) |
| `ontouchmove` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) |
| `ontouchstart` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) |
| `ontransitioncancel` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) |
| `ontransitionend` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) |
| `ontransitionrun` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) |
| `ontransitionstart` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) |

View File

@ -1,126 +0,0 @@
---
title: Choosing a web library
---
## Introduction
Yew apps can be built using either [`web-sys`](https://docs.rs/web-sys) or [`stdweb`](https://docs.rs/stdweb).
These two crates provide the bindings between Rust and Web APIs. You'll need to choose one or the other when adding
`yew` to your cargo dependencies:
```toml
# Choose `web-sys`
yew = "0.17"
# Choose `stdweb`
yew = { version = "0.17", package = "yew-stdweb" }
```
We recommend using `web-sys` due to its support from the [Rust / Wasm Working Group](https://rustwasm.github.io/).
:::warning
Yew will freeze support for `stdweb` at v0.18.
It will still receive patch fixes, but no new features will be added.
See [#1569](https://github.com/yewstack/yew/issues/1569)
:::
## Example Usage
This example illustrates the difference in how the two libraries are used.
You don't need to run this yourself.
```rust
// web-sys
let window: web_sys::Window = web_sys::window().expect("window not available");
window.alert_with_message("hello from wasm!").expect("alert failed");
// stdweb
let window: stdweb::web::Window = stdweb::web::window();
window.alert("hello from wasm!");
// stdweb with js! macro
use stdweb::js;
use stdweb::unstable::TryFrom;
use stdweb::web::Window;
let window_val: stdweb::Value = js!{ return window; }; // <- JS syntax inside!
let window = Window::try_from(window_val).expect("conversion to window failed");
window.alert("hello from wasm!");
```
The APIs for the two crates differ slightly but they serve roughly the same purpose.
## Choosing One
There are a few different angles to consider when choosing between using `web-sys` and `stdweb` for your app.
Note that it's possible to use both in one app, but to minimize the binary size of your compiled crate it's best to use only one of the two.
<table>
<thead>
<tr>
<th style="text-align:left"></th>
<th style="text-align:left"><code>web-sys</code>
</th>
<th style="text-align:left"><code>stdweb</code>
</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">Project Status</td>
<td style="text-align:left">Actively maintained by the <a href="https://rustwasm.github.io/">Rust / Wasm Working Group</a>
</td>
<td style="text-align:left">No Github activity for over 8 months</td>
</tr>
<tr>
<td style="text-align:left">Web API Coverage</td>
<td style="text-align:left">Rust APIs are generated from the Web IDL spec</td>
<td style="text-align:left">Browser APIs are added as needed by the community</td>
</tr>
<tr>
<td style="text-align:left">Rust API Design</td>
<td style="text-align:left">Takes conservative approach by returning <code>Result</code> for most API
calls</td>
<td style="text-align:left">Often avoids <code>Result</code> in favor of panics. For instance, <code>stdweb::web::window()</code> will
panic when called in a worker</td>
</tr>
<tr>
<td style="text-align:left">Supported Build Tools</td>
<td style="text-align:left">
<p></p>
<ul>
<li><code>trunk</code>
</li>
<li><code>wasm-pack</code>
</li>
</ul>
</td>
<td style="text-align:left">
<p></p>
<ul>
<li><code>cargo-web</code>
</li>
</ul>
</td>
</tr>
<tr>
<td style="text-align:left">Supported Targets</td>
<td style="text-align:left">
<ul>
<li><code>wasm32-unknown-unknown</code>
</li>
</ul>
</td>
<td style="text-align:left">
<ul>
<li><code>wasm32-unknown-unknown</code>
</li>
<li><code>wasm32-unknown-emscripten</code>
</li>
<li><code>asmjs-unknown-emscripten</code>
</li>
</ul>
</td>
</tr>
</tbody>
</table>

View File

@ -43,23 +43,15 @@ Because of this, it can only build libraries and doesn't provide useful tools li
[Get started with `wasm-pack`](project-setup/using-wasm-pack.md)
### [**`cargo-web`**](https://github.com/koute/cargo-web)
This was the best preferred tool to use before the creation of `wasm-bindgen`.
[Getting started with `cargo web`](project-setup/using-cargo-web.md)
### Comparison
| | `trunk` | `wasm-pack` | `cargo-web` |
| ----------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Project Status | Actively maintained | Actively maintained by the [Rust / Wasm Working Group](https://rustwasm.github.io) | No Github activity for over 6 months |
| Dev Experience | Just works! Batteries included, no external dependencies needed. | Bare-bones. You'll need to write some scripts to streamline the experience or use the webpack plugin. | Works great for code but needs separate asset pipeline. |
| Local Server | Supported | Only with webpack plugin | Supported |
| Auto rebuild on local changes | Supported | Only with webpack plugin | Supported |
| Asset handling | Supported | Only with webpack plugin | Static assets only |
| Headless Browser Testing | [In Progress](https://github.com/thedodd/trunk/issues/20) | [Supported](https://rustwasm.github.io/wasm-pack/book/commands/test.html) | [Supported](https://github.com/koute/cargo-web#features) |
| Supported Targets | <ul><li><code>wasm32-unknown-unknown</code></li></ul> | <ul><li><code>wasm32-unknown-unknown</code></li></ul> | <ul> <li><code>wasm32-unknown-unknown</code></li> <li><code>wasm32-unknown-emscripten</code></li> <li><code>asmjs-unknown-emscripten</code></li> </ul> |
| `web-sys` | Compatible | Compatible | Incompatible |
| `stdweb` | Incompatible | Compatible | Compatible |
| Example Usage | [Sample app](./build-a-sample-app.md) | [Starter template](https://github.com/yewstack/yew-wasm-pack-minimal) | [Build script](https://www.github.com/yewstack/yew/tree/master/packages/yew-stdweb/examples) for `yew-stdweb` examples |
| | `trunk` | `wasm-pack` |
| ----------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| Project Status | Actively maintained | Actively maintained by the [Rust / Wasm Working Group](https://rustwasm.github.io) |
| Dev Experience | Just works! Batteries included, no external dependencies needed. | Bare-bones. You'll need to write some scripts to streamline the experience or use the webpack plugin. |
| Local Server | Supported | Only with webpack plugin |
| Auto rebuild on local changes | Supported | Only with webpack plugin |
| Asset handling | Supported | Only with webpack plugin |
| Headless Browser Testing | [In Progress](https://github.com/thedodd/trunk/issues/20) | [Supported](https://rustwasm.github.io/wasm-pack/book/commands/test.html) |
| Supported Targets | <ul><li><code>wasm32-unknown-unknown</code></li></ul> | <ul><li><code>wasm32-unknown-unknown</code></li></ul> |
| Example Usage | [Sample app](./build-a-sample-app.md) | [Starter template](https://github.com/yewstack/yew-wasm-pack-minimal) |

View File

@ -1,35 +0,0 @@
---
title: Using cargo-web
---
Cargo web is a cargo subcommand for building client web apps. It makes building and deploying web
applications incredibly easy. It is also the only toolchain that supports Emscripten targets. Read
more [here](https://github.com/koute/cargo-web).
**Install**
```bash
cargo install cargo-web
```
## Build
```bash
cargo web build
```
## Run
```bash
cargo web start
```
## Supported Targets
* `wasm32-unknown-unknown`
* `wasm32-unknown-emscripten`
* `asmjs-unknown-emscripten`
:::note
For `*-emscripten` targets, you'll need to install the Emscripten SDK
:::

View File

@ -32,10 +32,7 @@ edition = "2018"
crate-type = ["rlib", "cdylib"]
[dependencies]
# for web_sys
yew = "0.17"
# or for stdweb
# yew = { version = "0.17", package = "yew-stdweb" }
wasm-bindgen = "0.2"
```

View File

@ -4,14 +4,9 @@ title: Debugging
## Panics
We **strongly recommend** the [`console_error_panic`](https://github.com/rustwasm/console_error_panic_hook)
which catches `panic!`s and outputs them to the console. Unfortunately this is not compatible with
apps built using `cargo-web`. **You probably don't need to enable this manually.** If you mount your
application using `yew::start_app()`, Yew will automatically catch `panic!`s and log them to your
browser's console. In some situations you might not be able to use `yew::start_app()` to mount your
application, in which case you can call `yew::initialize()` before starting your application to
configure this. Under the hood `yew::start_app()` calls `yew::initialize()` (which will enable the
`panic!` hook).
The [`console_error_panic`](https://github.com/rustwasm/console_error_panic_hook) crate catches
`panic!`s and outputs them to the console. Yew will automatically catch `panic!`s and log them to
your browser's console.
## Console Logging

View File

@ -61,7 +61,6 @@ fn create_canvas(document: &Document) -> HtmlCanvasElement {
}
fn main() {
yew::initialize();
let document = yew::utils::document();
let body = document.query_selector("body").unwrap().unwrap();
@ -76,7 +75,4 @@ fn main() {
body.append_child(&mount_point).unwrap();
yew::App::<Model>::new().mount(mount_point);
// only required for stdweb
yew::run_loop();
}

View File

@ -81,15 +81,10 @@ fn mount_app(selector: &'static str, app: App<Model>) -> ComponentLink<Model> {
}
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::SetOpposite(to_second.clone()));
to_second.send_message(Msg::SetOpposite(to_first));
// this is only important for stdweb
yew::run_loop();
}

View File

@ -31,4 +31,3 @@ yew = { path = "../yew" }
[features]
doc_test = []
std_web = []

View File

@ -35,21 +35,6 @@ impl Parse for ElementProps {
let listeners =
props.drain_filter(|prop| LISTENER_SET.contains(prop.label.to_string().as_str()));
#[cfg(feature = "std_web")]
listeners.check_all(|prop| {
let label = &prop.label;
if UNSUPPORTED_LISTENER_SET.contains(label.to_string().as_str()) {
Err(syn::Error::new_spanned(
&label,
format!(
"the listener `{}` is only available when using web-sys",
&label
),
))
} else {
Ok(())
}
})?;
// Multiple listener attributes are allowed, but no others
props.check_no_duplicates()?;
@ -225,53 +210,3 @@ lazy_static! {
.collect()
};
}
#[cfg(feature = "std_web")]
lazy_static! {
static ref UNSUPPORTED_LISTENER_SET: HashSet<&'static str> = {
vec![
"oncancel",
"oncanplay",
"oncanplaythrough",
"onclose",
"oncuechange",
"ondurationchange",
"onemptied",
"onended",
"onformdata",
"oninvalid",
"onloadeddata",
"onloadedmetadata",
"onpause",
"onplay",
"onplaying",
"onratechange",
"onreset",
"onsecuritypolicyviolation",
"onseeked",
"onseeking",
"onselect",
"onstalled",
"onsuspend",
"ontimeupdate",
"ontoggle",
"onvolumechange",
"onwaiting",
"oncopy",
"oncut",
"onpaste",
"onanimationcancel",
"onanimationend",
"onanimationiteration",
"onanimationstart",
"onselectstart",
"onshow",
"ontransitioncancel",
"ontransitionend",
"ontransitionrun",
"ontransitionstart",
]
.into_iter()
.collect()
};
}

View File

@ -34,9 +34,9 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
15 | <>{ for (0..3).map(|_| not_tree()) }</>
| ^^^^^^ `()` cannot be formatted with the default formatter
|
::: $WORKSPACE/packages/yew/src/utils.rs
::: $WORKSPACE/packages/yew/src/utils.rs:51:8
|
| T: Into<R>,
51 | T: Into<R>,
| ------- required by this bound in `yew::utils::into_node_iter`
|
= help: the trait `std::fmt::Display` is not implemented for `()`

View File

@ -71,9 +71,9 @@ error[E0277]: `()` is not an iterator
18 | { for () }
| ^^ `()` is not an iterator
|
::: $WORKSPACE/packages/yew/src/utils.rs
::: $WORKSPACE/packages/yew/src/utils.rs:50:9
|
| IT: IntoIterator<Item = T>,
50 | IT: IntoIterator<Item = T>,
| ---------------------- required by this bound in `yew::utils::into_node_iter`
|
= help: the trait `std::iter::Iterator` is not implemented for `()`

View File

@ -11,44 +11,29 @@ description = "A router implementation for the Yew framework"
repository = "https://github.com/yewstack/yew"
[features]
default = ["web_sys", "core", "unit_alias"]
default = ["core", "unit_alias"]
core = ["router", "components"] # Most everything
unit_alias = [] # TODO remove this
router = ["agent"] # The Router component
components = ["agent" ] # The button and anchor
agent = ["service"] # The RouteAgent
service = ["yew"] # The RouteService
std_web = [
"yew/std_web",
"stdweb"
]
web_sys = [
"yew/web_sys",
"gloo",
"js-sys",
"web-sys",
"wasm-bindgen"
]
[dependencies]
yew = { version = "0.17.0", path = "../yew", features = ["agent"], default-features= false, optional = true }
yew-router-macro = { version = "0.14.0", path = "../yew-router-macro" }
yew-router-route-parser = { version = "0.14.0", path = "../yew-router-route-parser" }
cfg-if = "1.0.0"
cfg-match = "0.2"
gloo = { version = "0.2.0", optional = true }
js-sys = { version = "0.3.35", optional = true }
gloo = "0.2.0"
js-sys = "0.3.35"
log = "0.4.8"
nom = "5.1.1"
serde = { version = "1.0.104", features = ["derive"] }
serde_json = "1.0.48"
stdweb = { version = "0.4.20", optional = true }
wasm-bindgen = { version = "0.2.58", optional = true }
wasm-bindgen = "0.2.58"
[dependencies.web-sys]
version = "0.3"
optional = true
features = [
'History',
'HtmlLinkElement',
@ -58,9 +43,5 @@ features = [
'Window',
]
# Compat with building yew with wasm-pack support.
[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies]
wasm-bindgen = "0.2.58"
[dev-dependencies]
uuid = "0.8.1"

View File

@ -47,12 +47,6 @@ impl<SW: Switch + Clone + 'static, STATE: RouterState> Component for RouterButto
}
fn view(&self) -> VNode {
#[cfg(feature = "std_web")]
let cb = self.link.callback(|event: ClickEvent| {
event.prevent_default();
Msg::Clicked
});
#[cfg(feature = "web_sys")]
let cb = self.link.callback(|event: MouseEvent| {
event.prevent_default();
Msg::Clicked

View File

@ -53,18 +53,9 @@ impl<SW: Switch + Clone + 'static, STATE: RouterState> Component for RouterAncho
}
fn view(&self) -> VNode {
#[cfg(feature = "std_web")]
use stdweb::web::event::IEvent;
let route: Route<STATE> = Route::from(self.props.route.clone());
let target: &str = route.as_str();
#[cfg(feature = "std_web")]
let cb = self.link.callback(|event: ClickEvent| {
event.prevent_default();
Msg::Clicked
});
#[cfg(feature = "web_sys")]
let cb = self.link.callback(|event: MouseEvent| {
event.prevent_default();
Msg::Clicked

View File

@ -1,14 +1,7 @@
//! Wrapper around route url string, and associated history state.
use cfg_if::cfg_if;
#[cfg(feature = "service")]
use serde::de::DeserializeOwned;
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::{unstable::TryFrom, Value};
}
}
use serde::{Deserialize, Serialize};
use std::{
fmt::{self, Debug},
@ -16,21 +9,7 @@ use std::{
};
/// Any state that can be used in the router agent must meet the criteria of this trait.
#[cfg(all(feature = "service", feature = "std_web"))]
pub trait RouteState:
Serialize + DeserializeOwned + Debug + Clone + Default + TryFrom<Value> + 'static
{
}
#[cfg(all(feature = "service", feature = "std_web"))]
impl<T> RouteState for T where
T: Serialize + DeserializeOwned + Debug + Clone + Default + TryFrom<Value> + 'static
{
}
/// Any state that can be used in the router agent must meet the criteria of this trait.
#[cfg(all(feature = "service", feature = "web_sys"))]
pub trait RouteState: Serialize + DeserializeOwned + Debug + Clone + Default + 'static {}
#[cfg(all(feature = "service", feature = "web_sys"))]
impl<T> RouteState for T where T: Serialize + DeserializeOwned + Debug + Clone + Default + 'static {}
/// The representation of a route, segmented into different sections for easy access.

View File

@ -3,24 +3,11 @@
use yew::callback::Callback;
use crate::route::{Route, RouteState};
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use std::marker::PhantomData;
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::{
js,
unstable::{TryFrom, TryInto},
web::{event::PopStateEvent, window, EventListenerHandle, History, IEventTarget, Location},
Value,
};
} else if #[cfg(feature = "web_sys")] {
use web_sys::{History, Location, PopStateEvent};
use gloo::events::EventListener;
use wasm_bindgen::{JsValue as Value, JsCast};
}
}
use gloo::events::EventListener;
use wasm_bindgen::{JsCast, JsValue as Value};
use web_sys::{History, Location, PopStateEvent};
/// A service that facilitates manipulation of the browser's URL bar and responding to browser events
/// when users press 'forward' or 'back'.
@ -30,9 +17,6 @@ cfg_if! {
pub struct RouteService<STATE = ()> {
history: History,
location: Location,
#[cfg(feature = "std_web")]
event_listener: Option<EventListenerHandle>,
#[cfg(feature = "web_sys")]
event_listener: Option<EventListener>,
phantom_data: PhantomData<STATE>,
}
@ -49,20 +33,14 @@ where
impl<T> RouteService<T> {
/// Creates the route service.
pub fn new() -> RouteService<T> {
let (history, location) = cfg_match! {
feature = "std_web" => ({
(
window().history(),
window().location().expect("browser does not support location API")
)
}),
feature = "web_sys" => ({
let window = web_sys::window().unwrap();
(
window.history().expect("browser does not support history API"),
window.location()
)
}),
let (history, location) = {
let window = web_sys::window().unwrap();
(
window
.history()
.expect("browser does not support history API"),
window.location(),
)
};
RouteService {
@ -107,10 +85,7 @@ where
pub fn register_callback(&mut self, callback: Callback<Route<STATE>>) {
let cb = move |event: PopStateEvent| {
let state_value: Value = event.state();
let state_string: String = cfg_match! {
feature = "std_web" => String::try_from(state_value).unwrap_or_default(),
feature = "web_sys" => state_value.as_string().unwrap_or_default(),
};
let state_string: String = state_value.as_string().unwrap_or_default();
let state: STATE = serde_json::from_str(&state_string).unwrap_or_else(|_| {
log::error!("Could not deserialize state string");
STATE::default()
@ -118,27 +93,20 @@ where
// Can't use the existing location, because this is a callback, and can't move it in
// here.
let location: Location = cfg_match! {
feature = "std_web" => window().location().unwrap(),
feature = "web_sys" => web_sys::window().unwrap().location(),
};
let location: Location = web_sys::window().unwrap().location();
let route: String = Self::get_route_from_location(&location);
callback.emit(Route { route, state })
};
cfg_if! {
if #[cfg(feature = "std_web")] {
self.event_listener = Some(window().add_event_listener(move |event: PopStateEvent| {
cb(event)
}));
} else if #[cfg(feature = "web_sys")] {
self.event_listener = Some(EventListener::new(web_sys::window().unwrap().as_ref(), "popstate", move |event| {
let event: PopStateEvent = event.clone().dyn_into().unwrap();
cb(event)
}));
}
};
self.event_listener = Some(EventListener::new(
web_sys::window().unwrap().as_ref(),
"popstate",
move |event| {
let event: PopStateEvent = event.clone().dyn_into().unwrap();
cb(event)
},
));
}
/// Sets the browser's url bar to contain the provided route,
@ -150,14 +118,9 @@ where
log::error!("Could not serialize state string");
"".to_string()
});
cfg_match! {
feature = "std_web" => ({
self.history.push_state(state_string, "", Some(route));
}),
feature = "web_sys" => ({
let _ = self.history.push_state_with_url(&Value::from_str(&state_string), "", Some(route));
}),
};
let _ = self
.history
.push_state_with_url(&Value::from_str(&state_string), "", Some(route));
}
/// Replaces the route with another one removing the most recent history event and
@ -167,14 +130,9 @@ where
log::error!("Could not serialize state string");
"".to_string()
});
cfg_match! {
feature = "std_web" => ({
let _ = self.history.replace_state(state_string, "", Some(route));
}),
feature = "web_sys" => ({
let _ = self.history.replace_state_with_url(&Value::from_str(&state_string), "", Some(route));
}),
};
let _ =
self.history
.replace_state_with_url(&Value::from_str(&state_string), "", Some(route));
}
/// Gets the concatenated path, query, and fragment.
@ -216,17 +174,9 @@ pub(crate) fn format_route_string(path: &str, query: &str, fragment: &str) -> St
}
fn get_state(history: &History) -> Value {
cfg_match! {
feature = "std_web" => js!(
return @{history}.state;
),
feature = "web_sys" => history.state().unwrap(),
}
history.state().unwrap()
}
fn get_state_string(history: &History) -> Option<String> {
cfg_match! {
feature = "std_web" => get_state(history).try_into().ok(),
feature = "web_sys" => get_state(history).as_string(),
}
get_state(history).as_string()
}

View File

@ -5,24 +5,19 @@ authors = ["Hamza <muhammadhamza1311@gmail.com>"]
edition = "2018"
[dependencies]
anyhow = "1"
cfg-if = "1.0"
cfg-match = "0.2"
gloo = { version = "0.2.1", optional = true }
gloo = "0.2.1"
http = "0.2"
js-sys = { version = "0.3", optional = true }
js-sys = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
stdweb = { version = "0.4.20", optional = true }
thiserror = "1"
yew = { path = "../yew" }
wasm-bindgen = { version = "0.2.60" }
wasm-bindgen-futures = { version = "0.4", optional = true }
wasm-bindgen = "0.2.60"
wasm-bindgen-futures = "0.4"
[dependencies.web-sys]
version = "0.3"
optional = true
features = [
"AbortController",
"AbortSignal",
@ -50,21 +45,12 @@ features = [
"WorkerOptions",
]
[dev-dependencies]
wasm-bindgen-test = "0.3.4"
base64 = "0.13.0"
ssri = "6.0.0"
[features]
default = ["web_sys"]
web_sys = [
"gloo",
"js-sys",
"web-sys",
"wasm-bindgen-futures"
]
std_web = ["stdweb"]
wasm_test = []
httpbin_test = []
echo_server_test = []

View File

@ -1,147 +1,68 @@
#![cfg(test)]
#[cfg(feature = "web_sys")]
pub use self::web_sys::*;
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll, Waker};
use yew::callback::*;
#[cfg(feature = "std_web")]
pub use self::std_web::*;
struct CallbackHandle<T> {
waker: Option<Waker>,
output: Option<T>,
}
#[cfg(feature = "web_sys")]
mod web_sys {
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll, Waker};
use yew::callback::*;
struct CallbackHandle<T> {
waker: Option<Waker>,
output: Option<T>,
}
impl<T> Default for CallbackHandle<T> {
fn default() -> Self {
CallbackHandle {
waker: None,
output: None,
}
}
}
pub struct CallbackFuture<T>(Rc<RefCell<CallbackHandle<T>>>);
impl<T> Clone for CallbackFuture<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T> Default for CallbackFuture<T> {
fn default() -> Self {
Self(Rc::default())
}
}
impl<T: 'static> Into<Callback<T>> for CallbackFuture<T> {
fn into(self) -> Callback<T> {
Callback::from(move |r| self.finish(r))
}
}
impl<T> Future for CallbackFuture<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(output) = self.ready() {
Poll::Ready(output)
} else {
let handle = &self.0;
handle.borrow_mut().waker = Some(cx.waker().clone());
Poll::Pending
}
}
}
impl<T> CallbackFuture<T> {
pub fn ready(&self) -> Option<T> {
self.0.borrow_mut().output.take()
}
fn finish(&self, output: T) {
self.0.borrow_mut().output = Some(output);
if let Some(waker) = self.0.borrow_mut().waker.take() {
waker.wake();
}
impl<T> Default for CallbackHandle<T> {
fn default() -> Self {
CallbackHandle {
waker: None,
output: None,
}
}
}
#[cfg(feature = "std_web")]
mod std_web {
use std::cell::RefCell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll, Waker};
use yew::callback::*;
pub struct CallbackFuture<T>(Rc<RefCell<CallbackHandle<T>>>);
struct CallbackHandle<T> {
waker: Option<Waker>,
output: Option<T>,
impl<T> Clone for CallbackFuture<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T> Default for CallbackHandle<T> {
fn default() -> Self {
CallbackHandle {
waker: None,
output: None,
}
}
impl<T> Default for CallbackFuture<T> {
fn default() -> Self {
Self(Rc::default())
}
}
pub struct CallbackFuture<T>(Rc<RefCell<CallbackHandle<T>>>);
impl<T> Clone for CallbackFuture<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
impl<T: 'static> Into<Callback<T>> for CallbackFuture<T> {
fn into(self) -> Callback<T> {
Callback::from(move |r| self.finish(r))
}
}
impl<T> Default for CallbackFuture<T> {
fn default() -> Self {
Self(Rc::default())
}
}
impl<T: 'static> Into<Callback<T>> for CallbackFuture<T> {
fn into(self) -> Callback<T> {
Callback::from(move |r| self.finish(r))
}
}
impl<T> Future for CallbackFuture<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(output) = self.ready() {
Poll::Ready(output)
} else {
let handle = &self.0;
handle.borrow_mut().waker = Some(cx.waker().clone());
Poll::Pending
}
}
}
impl<T> CallbackFuture<T> {
pub fn ready(&self) -> Option<T> {
self.0.borrow_mut().output.take()
}
fn finish(&self, output: T) {
self.0.borrow_mut().output = Some(output);
if let Some(waker) = self.0.borrow_mut().waker.take() {
waker.wake();
}
impl<T> Future for CallbackFuture<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(output) = self.ready() {
Poll::Ready(output)
} else {
let handle = &self.0;
handle.borrow_mut().waker = Some(cx.waker().clone());
Poll::Pending
}
}
}
impl<T> CallbackFuture<T> {
pub fn ready(&self) -> Option<T> {
self.0.borrow_mut().output.take()
}
fn finish(&self, output: T) {
self.0.borrow_mut().output = Some(output);
if let Some(waker) = self.0.borrow_mut().waker.take() {
waker.wake();
}
}
}

View File

@ -1,16 +1,7 @@
//! This module contains a service implementation to use browser's console.
use cfg_if::cfg_if;
use cfg_match::cfg_match;
cfg_if! {
if #[cfg(feature = "std_web")] {
#[allow(unused_imports)]
use stdweb::{_js_impl, js};
} else if #[cfg(feature = "web_sys")] {
use wasm_bindgen::JsValue;
use web_sys::console;
}
}
use wasm_bindgen::JsValue;
use web_sys::console;
/// A service to use methods of a
/// [Console](https://developer.mozilla.org/en-US/docs/Web/API/Console).
@ -22,156 +13,105 @@ impl ConsoleService {
/// method implementation.
/// This method outputs the provided message to the console.
pub fn log(message: &str) {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.log(@{message}); },
feature = "web_sys" => console::log_1(&JsValue::from_str(message)),
};
console::log_1(&JsValue::from_str(message))
}
/// [console.warn](https://developer.mozilla.org/en-US/docs/Web/API/Console/warn)
/// method implementation.
/// This method outputs the provided message to the console as a warning.
pub fn warn(message: &str) {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.warn(@{message}); },
feature = "web_sys" => console::warn_1(&JsValue::from_str(message)),
};
console::warn_1(&JsValue::from_str(message))
}
/// [console.info](https://developer.mozilla.org/en-US/docs/Web/API/Console/info)
/// method implementation.
/// This method outputs the provided message to the console as information.
pub fn info(message: &str) {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.info(@{message}); },
feature = "web_sys" => console::info_1(&JsValue::from_str(message)),
};
console::info_1(&JsValue::from_str(message))
}
/// [console.error](https://developer.mozilla.org/en-US/docs/Web/API/Console/error)
/// method implementation.
/// This method outputs the provided message to the console as an error.
pub fn error(message: &str) {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.error(@{message}); },
feature = "web_sys" => console::error_1(&JsValue::from_str(message)),
};
console::error_1(&JsValue::from_str(message))
}
/// [console.debug](https://developer.mozilla.org/en-US/docs/Web/API/Console/debug)
/// method implementation.
pub fn debug(message: &str) {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.debug(@{message}); },
feature = "web_sys" => console::debug_1(&JsValue::from_str(message)),
};
console::debug_1(&JsValue::from_str(message))
}
/// [console.count_named](https://developer.mozilla.org/en-US/docs/Web/API/Console/count_named)
/// method implementation.
pub fn count_named(name: &str) {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.count(@{name}); },
feature = "web_sys" => console::count_with_label(name),
};
console::count_with_label(name)
}
/// [console.count](https://developer.mozilla.org/en-US/docs/Web/API/Console/count)
/// method implementation.
pub fn count() {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.count(); },
feature = "web_sys" => console::count(),
};
console::count()
}
/// [console.time_named](https://developer.mozilla.org/en-US/docs/Web/API/Console/time_named)
/// method implementation.
pub fn time_named(name: &str) {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.time(@{name}); },
feature = "web_sys" => console::time_with_label(name),
};
console::time_with_label(name)
}
/// [console.time_named_end](https://developer.mozilla.org/en-US/docs/Web/API/Console/time_named_end)
/// method implementation.
pub fn time_named_end(name: &str) {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.timeEnd(@{name}); },
feature = "web_sys" => console::time_end_with_label(name),
};
console::time_end_with_label(name)
}
/// [console.time](https://developer.mozilla.org/en-US/docs/Web/API/Console/time)
/// method implementation.
pub fn time() {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.time(); },
feature = "web_sys" => console::time(),
};
console::time()
}
/// [console.time_end](https://developer.mozilla.org/en-US/docs/Web/API/Console/time_end)
/// method implementation.
pub fn time_end() {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.timeEnd(); },
feature = "web_sys" => console::time_end(),
};
console::time_end()
}
/// [console.clear](https://developer.mozilla.org/en-US/docs/Web/API/Console/clear)
/// method implementation.
pub fn clear() {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.clear(); },
feature = "web_sys" => console::clear(),
};
console::clear()
}
/// [console.group](https://developer.mozilla.org/en-US/docs/Web/API/Console/group)
/// method implementation.
pub fn group() {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.group(); },
feature = "web_sys" => console::group_0(),
};
console::group_0()
}
/// [console.group_collapsed](https://developer.mozilla.org/en-US/docs/Web/API/Console/group_collapsed)
/// method implementation.
pub fn group_collapsed() {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.groupCollapsed(); },
feature = "web_sys" => console::group_collapsed_0(),
};
console::group_collapsed_0()
}
/// [console.group_end](https://developer.mozilla.org/en-US/docs/Web/API/Console/group_end)
/// method implementation.
pub fn group_end() {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.groupEnd(); },
feature = "web_sys" => console::group_end(),
};
console::group_end()
}
/// [console.trace](https://developer.mozilla.org/en-US/docs/Web/API/Console/trace)
/// method implementation.
/// This method outputs the current stack trace to the console.
pub fn trace() {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.trace(); },
feature = "web_sys" => console::trace_0(),
};
console::trace_0()
}
/// [console.assert](https://developer.mozilla.org/en-US/docs/Web/API/Console/assert)
/// method implementation.
pub fn assert(condition: bool, message: &str) {
cfg_match! {
feature = "std_web" => js! { @(no_return) console.assert(@{condition}, @{message}); },
feature = "web_sys" => console::assert_with_condition_and_data_1(condition, &String::from(message).into()),
};
console::assert_with_condition_and_data_1(condition, &String::from(message).into())
}
}

View File

@ -4,17 +4,7 @@
//! If you call these methods repeatably browsers tend to disable these options to give users
//! a better experience.
use cfg_if::cfg_if;
use cfg_match::cfg_match;
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 yew::utils;
}
}
use yew::utils;
/// A dialog service.
#[derive(Default, Debug)]
@ -24,33 +14,18 @@ impl DialogService {
/// Calls [alert](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert)
/// function.
pub fn alert(message: &str) {
cfg_match! {
feature = "std_web" => js! { @(no_return) alert(@{message}); },
feature = "web_sys" => utils::window().alert_with_message(message).unwrap(),
};
utils::window().alert_with_message(message).unwrap()
}
/// Calls [confirm](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm)
/// function.
pub fn confirm(message: &str) -> bool {
cfg_match! {
feature = "std_web" => ({
let value: Value = js! { return confirm(@{message}); };
match value {
Value::Bool(result) => result,
_ => false,
}
}),
feature = "web_sys" => utils::window().confirm_with_message(message).unwrap(),
}
utils::window().confirm_with_message(message).unwrap()
}
/// Prompts the user to input a message. In most browsers this will open an alert box with
/// an input field where the user can input a message.
#[cfg_attr(
feature = "web_sys",
doc = "A default value can be supplied which will be returned if the user doesn't input anything."
)]
#[doc = "A default value can be supplied which will be returned if the user doesn't input anything."]
///
/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt)
///
@ -61,40 +36,18 @@ impl DialogService {
/// the user inputs their message which means that the page will appear to have 'frozen'
/// while the user types in their message.
///
#[cfg_attr(
feature = "web_sys",
doc = "This function will return `None` if the value of `default` is `None` and the user \
#[doc = "This function will return `None` if the value of `default` is `None` and the user \
cancels the operation. (normally a 'cancel' button will be displayed to the user, \
clicking which cancels the operation)."
)]
#[cfg_attr(
feature = "std_web",
doc = "This function will return `None` if the user cancels the operation (normally a \
'cancel' button will be displayed to the user, clicking which cancels the operation)."
)]
pub fn prompt(
message: &str,
#[cfg(feature = "web_sys")] default: Option<&str>,
) -> Option<String> {
cfg_if! {
if #[cfg(feature="web_sys")] {
if let Some(default) = default {
utils::window()
.prompt_with_message_and_default(message, default)
.expect("Couldn't read input.")
}
else {
utils::window()
.prompt_with_message(message)
.expect("Couldn't read input.")
}
} else if #[cfg(feature="std_web")] {
let value: Value = js! { return prompt(@{message}); };
match value {
Value::String(result) => Some(result),
_ => None,
}
}
clicking which cancels the operation)."]
pub fn prompt(message: &str, default: Option<&str>) -> Option<String> {
if let Some(default) = default {
utils::window()
.prompt_with_message_and_default(message, default)
.expect("Couldn't read input.")
} else {
utils::window()
.prompt_with_message(message)
.expect("Couldn't read input.")
}
}
}

View File

@ -1,12 +1,571 @@
//! Service to send HTTP-request to a server.
cfg_if::cfg_if! {
if #[cfg(feature = "std_web")] {
mod std_web;
pub use std_web::*;
} else if #[cfg(feature = "web_sys")] {
mod web_sys;
pub use self::web_sys::*;
use crate::Task;
use anyhow::{anyhow, Error};
use http::request::Parts;
use js_sys::{Array, Promise, Uint8Array};
use std::cell::RefCell;
use std::fmt;
use std::iter::FromIterator;
use std::marker::PhantomData;
use std::rc::Rc;
use thiserror::Error as ThisError;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::{spawn_local, JsFuture};
use web_sys::{
AbortController, Headers, ReferrerPolicy, Request as WebRequest, RequestInit,
Response as WebResponse,
};
use yew::callback::Callback;
use yew::format::{Binary, Format, Text};
#[doc(no_inline)]
pub use web_sys::{
RequestCache as Cache, RequestCredentials as Credentials, RequestMode as Mode,
RequestRedirect as Redirect, Window, WorkerGlobalScope,
};
#[doc(no_inline)]
pub use http::{HeaderMap, Method, Request, Response, StatusCode, Uri};
trait JsInterop: Sized {
fn from_js(js_value: JsValue) -> Result<Self, FetchError>;
fn to_js(self) -> JsValue;
}
impl JsInterop for Vec<u8> {
fn from_js(js_value: JsValue) -> Result<Self, FetchError> {
Ok(Uint8Array::new(&js_value).to_vec())
}
fn to_js(self) -> JsValue {
Uint8Array::from(self.as_slice()).into()
}
}
impl JsInterop for String {
fn from_js(js_value: JsValue) -> Result<Self, FetchError> {
js_value.as_string().ok_or(FetchError::InternalError)
}
fn to_js(self) -> JsValue {
self.into()
}
}
/// Init options for `fetch()` function call.
/// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
#[derive(Default, Debug)]
pub struct FetchOptions {
/// Cache of a fetch request.
pub cache: Option<Cache>,
/// Credentials of a fetch request.
pub credentials: Option<Credentials>,
/// Redirect behaviour of a fetch request.
pub redirect: Option<Redirect>,
/// Request mode of a fetch request.
pub mode: Option<Mode>,
/// Referrer of a fetch request.
pub referrer: Option<Referrer>,
/// Referrer policy of a fetch request.
pub referrer_policy: Option<ReferrerPolicy>,
/// Integrity of a fetch request.
pub integrity: Option<String>,
}
impl Into<RequestInit> for FetchOptions {
fn into(self) -> RequestInit {
let mut init = RequestInit::new();
if let Some(cache) = self.cache {
init.cache(cache);
}
if let Some(credentials) = self.credentials {
init.credentials(credentials);
}
if let Some(redirect) = self.redirect {
init.redirect(redirect);
}
if let Some(mode) = self.mode {
init.mode(mode);
}
if let Some(referrer) = self.referrer {
match referrer {
Referrer::SameOriginUrl(referrer) => init.referrer(&referrer),
Referrer::AboutClient => init.referrer("about:client"),
Referrer::Empty => init.referrer(""),
};
}
if let Some(referrer_policy) = self.referrer_policy {
init.referrer_policy(referrer_policy);
}
if let Some(integrity) = self.integrity {
init.integrity(&integrity);
}
init
}
}
// convert `headers` to `Iterator<Item = (String, String)>`
fn header_iter(headers: Headers) -> impl Iterator<Item = (String, String)> {
js_sys::try_iter(&headers)
.unwrap()
.unwrap()
.map(Result::unwrap)
.map(|entry| {
let entry = Array::from(&entry);
let key = entry.get(0);
let value = entry.get(1);
(key.as_string().unwrap(), value.as_string().unwrap())
})
}
/// Represents errors of a fetch service.
#[derive(Debug, ThisError)]
enum FetchError {
#[error("canceled")]
Canceled,
#[error("{0}")]
FetchFailed(String),
#[error("invalid response")]
InvalidResponse,
#[error("unexpected error, please report")]
InternalError,
}
#[derive(Debug)]
struct Handle {
active: Rc<RefCell<bool>>,
abort_controller: Option<AbortController>,
}
/// A handle to control sent requests.
#[must_use = "the request will be cancelled when the task is dropped"]
pub struct FetchTask(Handle);
impl fmt::Debug for FetchTask {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("FetchTask")
}
}
/// A service to fetch resources.
#[derive(Default, Debug)]
pub struct FetchService {}
impl FetchService {
/// Sends a request to a remote server given a Request object and a callback
/// function to convert a Response object into a loop's message.
///
/// You may use a Request builder to build your request declaratively as on the
/// following examples:
///
/// ```
///# use serde_json::json;
///# use yew::format::{Nothing, Json};
///# use yew_services::fetch::Request;
/// let post_request = Request::post("https://my.api/v1/resource")
/// .header("Content-Type", "application/json")
/// .body(Json(&json!({"foo": "bar"})))
/// .expect("Failed to build request.");
///
/// let get_request = Request::get("https://my.api/v1/resource")
/// .body(Nothing)
/// .expect("Failed to build request.");
/// ```
///
/// The callback function can build a loop message by passing or analyzing the
/// response body and metadata.
///
/// ```
///# use anyhow::Error;
///# use yew::{Component, ComponentLink, Html};
///# use yew_services::FetchService;
///# use yew_services::fetch::{Response, Request};
///# struct Comp;
///# impl Component for Comp {
///# type Message = Msg;type Properties = ();
///# fn create(props: Self::Properties,link: ComponentLink<Self>) -> Self {unimplemented!()}
///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()}
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
///# fn view(&self) -> Html {unimplemented!()}
///# }
///# enum Msg {
///# Noop,
///# Error
///# }
///# fn dont_execute() {
///# let link: ComponentLink<Comp> = unimplemented!();
///# let post_request: Request<Result<String, Error>> = unimplemented!();
/// let task = FetchService::fetch(
/// post_request,
/// link.callback(|response: Response<Result<String, Error>>| {
/// if response.status().is_success() {
/// Msg::Noop
/// } else {
/// Msg::Error
/// }
/// }),
/// );
///# }
/// ```
///
/// For a full example, you can specify that the response must be in the JSON format,
/// and be a specific serialized data type. If the message isn't JSON, or isn't the specified
/// data type, then you will get an error message.
///
/// ```
///# use anyhow::Error;
///# use http::Request;
///# use serde::Deserialize;
///# use yew::{Component, ComponentLink, Html};
///# use yew::format::{Json, Nothing, Format};
///# use yew_services::fetch::Response;
///# use yew_services::FetchService;
///# struct Comp;
///# impl Component for Comp {
///# type Message = Msg;type Properties = ();
///# fn create(props: Self::Properties,link: ComponentLink<Self>) -> Self {unimplemented!()}
///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()}
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
///# fn view(&self) -> Html {unimplemented!()}
///# }
///# enum Msg {
///# FetchResourceComplete(Data),
///# FetchResourceFailed
///# }
/// #[derive(Deserialize)]
/// struct Data {
/// value: String
/// }
///
///# fn dont_execute() {
///# let link: ComponentLink<Comp> = unimplemented!();
/// let get_request = Request::get("/thing").body(Nothing).unwrap();
/// let callback = link.callback(|response: Response<Json<Result<Data, Error>>>| {
/// if let (meta, Json(Ok(body))) = response.into_parts() {
/// if meta.status.is_success() {
/// return Msg::FetchResourceComplete(body);
/// }
/// }
/// Msg::FetchResourceFailed
/// });
///
/// let task = FetchService::fetch(get_request, callback);
///# }
/// ```
///
pub fn fetch<IN, OUT: 'static>(
request: Request<IN>,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, Error>
where
IN: Into<Text>,
OUT: From<Text>,
{
fetch_impl::<IN, OUT, String>(false, request, None, callback)
}
/// `fetch` with provided `FetchOptions` object.
/// Use it if you need to send cookies with a request:
/// ```
///# use anyhow::Error;
///# use http::Response;
///# use yew::format::Nothing;
///# use yew::{Html, Component, ComponentLink};
///# use yew_services::fetch::{self, FetchOptions, FetchService, Credentials};
///# struct Comp;
///# impl Component for Comp {
///# type Message = Msg;
///# type Properties = ();
///# fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {unimplemented!()}
///# fn update(&mut self, msg: Self::Message) -> bool {unimplemented!()}
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
///# fn view(&self) -> Html {unimplemented!()}
///# }
///# pub enum Msg {}
///# fn dont_execute() {
///# let link: ComponentLink<Comp> = unimplemented!();
///# let callback = link.callback(|response: Response<Result<String, Error>>| -> Msg { unimplemented!() });
/// let request = fetch::Request::get("/path/")
/// .body(Nothing)
/// .unwrap();
/// let options = FetchOptions {
/// credentials: Some(Credentials::SameOrigin),
/// ..FetchOptions::default()
/// };
/// let task = FetchService::fetch_with_options(request, options, callback);
///# }
/// ```
pub fn fetch_with_options<IN, OUT: 'static>(
request: Request<IN>,
options: FetchOptions,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, Error>
where
IN: Into<Text>,
OUT: From<Text>,
{
fetch_impl::<IN, OUT, String>(false, request, Some(options), callback)
}
/// Fetch the data in binary format.
pub fn fetch_binary<IN, OUT: 'static>(
request: Request<IN>,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, Error>
where
IN: Into<Binary>,
OUT: From<Binary>,
{
fetch_impl::<IN, OUT, Vec<u8>>(true, request, None, callback)
}
/// Fetch the data in binary format with the provided request options.
pub fn fetch_binary_with_options<IN, OUT: 'static>(
request: Request<IN>,
options: FetchOptions,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, Error>
where
IN: Into<Binary>,
OUT: From<Binary>,
{
fetch_impl::<IN, OUT, Vec<u8>>(true, request, Some(options), callback)
}
}
fn fetch_impl<IN, OUT: 'static, DATA: 'static>(
binary: bool,
request: Request<IN>,
options: Option<FetchOptions>,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, Error>
where
DATA: JsInterop,
IN: Into<Format<DATA>>,
OUT: From<Format<DATA>>,
{
// Transform http::Request into WebRequest.
let (parts, body) = request.into_parts();
let body = match body.into() {
Ok(b) => b.to_js(),
Err(_) => JsValue::NULL,
};
let request = build_request(parts, &body)?;
// Transform FetchOptions into RequestInit.
let abort_controller = AbortController::new().ok();
let mut init = options.map_or_else(RequestInit::new, Into::into);
if let Some(abort_controller) = &abort_controller {
init.signal(Some(&abort_controller.signal()));
}
// Start fetch
let promise = GLOBAL.with(|global| global.fetch_with_request_and_init(&request, &init));
// Spawn future to resolve fetch
let active = Rc::new(RefCell::new(true));
let data_fetcher = DataFetcher::new(binary, callback, active.clone());
spawn_local(DataFetcher::fetch_data(data_fetcher, promise));
Ok(FetchTask(Handle {
active,
abort_controller,
}))
}
struct DataFetcher<OUT: 'static, DATA>
where
DATA: JsInterop,
OUT: From<Format<DATA>>,
{
binary: bool,
active: Rc<RefCell<bool>>,
callback: Callback<Response<OUT>>,
_marker: PhantomData<DATA>,
}
impl<OUT: 'static, DATA> DataFetcher<OUT, DATA>
where
DATA: JsInterop,
OUT: From<Format<DATA>>,
{
fn new(binary: bool, callback: Callback<Response<OUT>>, active: Rc<RefCell<bool>>) -> Self {
Self {
binary,
callback,
active,
_marker: PhantomData::default(),
}
}
async fn fetch_data(self, promise: Promise) {
let result = self.fetch_data_impl(promise).await;
let (data, status, headers) = match result {
Ok((data, response)) => (Ok(data), response.status(), Some(response.headers())),
Err(err) => (Err(err), 408, None),
};
self.callback(data, status, headers);
}
async fn fetch_data_impl(&self, promise: Promise) -> Result<(DATA, WebResponse), Error> {
let response = self.get_response(promise).await?;
let data = self.get_data(&response).await?;
Ok((data, response))
}
// Prepare the response callback.
// Notice that the callback signature must match the call from the javascript
// side. There is no static check at this point.
fn callback(&self, data: Result<DATA, Error>, status: u16, headers: Option<Headers>) {
let mut response_builder = Response::builder();
if let Ok(status) = StatusCode::from_u16(status) {
response_builder = response_builder.status(status);
}
if let Some(headers) = headers {
for (key, value) in header_iter(headers) {
response_builder = response_builder.header(key.as_str(), value.as_str());
}
}
// Deserialize and wrap response data into a Text or Binary object.
let response = response_builder
.body(OUT::from(data))
.expect("failed to build response, please report");
*self.active.borrow_mut() = false;
self.callback.emit(response);
}
async fn get_response(&self, fetch_promise: Promise) -> Result<WebResponse, FetchError> {
let response = JsFuture::from(fetch_promise)
.await
.map_err(|err| err.unchecked_into::<js_sys::Error>())
.map_err(|err| FetchError::FetchFailed(err.to_string().as_string().unwrap()))?;
if *self.active.borrow() {
Ok(WebResponse::from(response))
} else {
Err(FetchError::Canceled)
}
}
async fn get_data(&self, response: &WebResponse) -> Result<DATA, FetchError> {
let data_promise = if self.binary {
response.array_buffer()
} else {
response.text()
}
.map_err(|_| FetchError::InvalidResponse)?;
let data_result = JsFuture::from(data_promise).await;
if *self.active.borrow() {
data_result
.map_err(|_| FetchError::InvalidResponse)
.and_then(DATA::from_js)
} else {
Err(FetchError::Canceled)
}
}
}
fn build_request(parts: Parts, body: &JsValue) -> Result<WebRequest, Error> {
// Map headers into a Js `Header` type.
let header_list = parts
.headers
.iter()
.map(|(k, v)| {
Ok(Array::from_iter(&[
JsValue::from_str(k.as_str()),
JsValue::from_str(
v.to_str()
.map_err(|_| anyhow!("Unparsable request header"))?,
),
]))
})
.collect::<Result<Array, Error>>()?;
let header_map = Headers::new_with_str_sequence_sequence(&header_list)
.map_err(|_| anyhow!("couldn't build headers"))?;
// Formats URI.
let uri = parts.uri.to_string();
let method = parts.method.as_str();
let mut init = RequestInit::new();
init.method(method).body(Some(body)).headers(&header_map);
WebRequest::new_with_str_and_init(&uri, &init).map_err(|_| anyhow!("failed to build request"))
}
impl Task for FetchTask {
fn is_active(&self) -> bool {
*self.0.active.borrow()
}
}
impl Drop for FetchTask {
fn drop(&mut self) {
if self.is_active() {
// Fetch API doesn't support request cancelling in all browsers
// and we should use this workaround with a flag.
// In that case, request not canceled, but callback won't be called.
*self.0.active.borrow_mut() = false;
if let Some(abort_controller) = &self.0.abort_controller {
abort_controller.abort();
}
}
}
}
thread_local! {
static GLOBAL: WindowOrWorker = WindowOrWorker::new();
}
enum WindowOrWorker {
Window(Window),
Worker(WorkerGlobalScope),
}
impl WindowOrWorker {
fn new() -> Self {
#[wasm_bindgen]
extern "C" {
type Global;
#[wasm_bindgen(method, getter, js_name = Window)]
fn window(this: &Global) -> JsValue;
#[wasm_bindgen(method, getter, js_name = WorkerGlobalScope)]
fn worker(this: &Global) -> JsValue;
}
let global: Global = js_sys::global().unchecked_into();
if !global.window().is_undefined() {
Self::Window(global.unchecked_into())
} else if !global.worker().is_undefined() {
Self::Worker(global.unchecked_into())
} else {
panic!(
"Yew's `FetchService` only works when a `window` or `worker` object is available."
);
}
}
}
impl WindowOrWorker {
fn fetch_with_request_and_init(&self, input: &WebRequest, init: &RequestInit) -> Promise {
match self {
Self::Window(window) => window.fetch_with_request_and_init(input, init),
Self::Worker(worker) => worker.fetch_with_request_and_init(input, init),
}
}
}
@ -26,12 +585,11 @@ pub enum Referrer {
mod tests {
use super::*;
use crate::callback_test_util::CallbackFuture;
#[cfg(feature = "web_sys")]
use ::web_sys::ReferrerPolicy;
use serde::Deserialize;
use ssri::Integrity;
use std::collections::HashMap;
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
use web_sys::ReferrerPolicy;
use yew::callback::Callback;
use yew::format::{Json, Nothing};
use yew::utils;
@ -260,9 +818,6 @@ mod tests {
let callback: Callback<_> = cb_future.clone().into();
let _task = FetchService::fetch(request, callback);
let resp = cb_future.await;
#[cfg(feature = "std_web")]
assert!(resp.body().is_err());
#[cfg(feature = "web_sys")]
assert!(resp
.body()
.as_ref()

View File

@ -1,503 +0,0 @@
//! `stdweb` implementation for the fetch service.
use super::Referrer;
use crate::Task;
use serde::Serialize;
use std::collections::HashMap;
use std::fmt;
use stdweb::serde::Serde;
use stdweb::unstable::{TryFrom, TryInto};
use stdweb::web::error::Error;
use stdweb::web::ArrayBuffer;
use stdweb::{JsSerialize, Value};
#[allow(unused_imports)]
use stdweb::{_js_impl, js};
use thiserror::Error;
use yew::callback::Callback;
use yew::format::{Binary, Format, Text};
#[doc(no_inline)]
pub use http::{HeaderMap, Method, Request, Response, StatusCode, Uri};
/// Type to set cache for fetch.
#[derive(Serialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum Cache {
/// `default` value of cache.
#[serde(rename = "default")]
DefaultCache,
/// `no-store` value of cache.
NoStore,
/// `reload` value of cache.
Reload,
/// `no-cache` value of cache.
NoCache,
/// `force-cache` value of cache
ForceCache,
/// `only-if-cached` value of cache
OnlyIfCached,
}
/// Type to set credentials for fetch.
#[derive(Serialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum Credentials {
/// `omit` value of credentials.
Omit,
/// `include` value of credentials.
Include,
/// `same-origin` value of credentials.
SameOrigin,
}
/// Type to set mode for fetch.
#[derive(Serialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum Mode {
/// `same-origin` value of mode.
SameOrigin,
/// `no-cors` value of mode.
NoCors,
/// `cors` value of mode.
Cors,
}
/// Type to set redirect behaviour for fetch.
#[derive(Serialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum Redirect {
/// `follow` value of redirect.
Follow,
/// `error` value of redirect.
Error,
/// `manual` value of redirect.
Manual,
}
impl Serialize for Referrer {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match *self {
Referrer::SameOriginUrl(ref s) => serializer.serialize_str(s),
Referrer::AboutClient => {
serializer.serialize_unit_variant("Referrer", 0, "about:client")
}
Referrer::Empty => serializer.serialize_unit_variant("Referrer", 1, ""),
}
}
}
/// Type to set referrer policy for fetch.
#[derive(Serialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum ReferrerPolicy {
/// `no-referrer` value of referrerPolicy.
NoReferrer,
/// `no-referrer-when-downgrade` value of referrerPolicy.
NoReferrerWhenDowngrade,
/// `same-origin` value of referrerPolicy.
SameOrigin,
/// `origin` value of referrerPolicy.
Origin,
/// `strict-origin` value of referrerPolicy.
StrictOrigin,
/// `origin-when-cross-origin` value of referrerPolicy.
OriginWhenCrossOrigin,
/// `strict-origin-when-cross-origin` value of referrerPolicy.
StrictOriginWhenCrossOrigin,
/// `unsafe-url` value of referrerPolicy.
UnsafeUrl,
}
/// Init options for `fetch()` function call.
/// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
#[derive(Serialize, Default, Debug)]
#[serde(rename_all = "camelCase")]
pub struct FetchOptions {
/// Cache of a fetch request.
#[serde(skip_serializing_if = "Option::is_none")]
pub cache: Option<Cache>,
/// Credentials of a fetch request.
#[serde(skip_serializing_if = "Option::is_none")]
pub credentials: Option<Credentials>,
/// Redirect behaviour of a fetch request.
#[serde(skip_serializing_if = "Option::is_none")]
pub redirect: Option<Redirect>,
/// Request mode of a fetch request.
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<Mode>,
/// Referrer of a fetch request.
#[serde(skip_serializing_if = "Option::is_none")]
pub referrer: Option<Referrer>,
/// Referrer policy of a fetch request.
#[serde(skip_serializing_if = "Option::is_none")]
pub referrer_policy: Option<ReferrerPolicy>,
/// Integrity of a fetch request.
#[serde(skip_serializing_if = "Option::is_none")]
pub integrity: Option<String>,
}
/// Represents errors of a fetch service.
#[derive(Debug, Error)]
enum FetchError {
#[error("failed response")]
FailedResponse,
}
/// A handle to control sent requests. Can be canceled with a `Task::cancel` call.
#[must_use = "the request will be cancelled when the task is dropped"]
pub struct FetchTask(Option<Value>);
impl fmt::Debug for FetchTask {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("FetchTask")
}
}
/// A service to fetch resources.
#[derive(Default, Debug)]
pub struct FetchService {}
impl FetchService {
/// Sends a request to a remote server given a Request object and a callback
/// function to convert a Response object into a loop's message.
///
/// You may use a Request builder to build your request declaratively as on the
/// following examples:
///
/// ```
///# use yew::format::{Nothing, Json};
///# use yew_services::fetch::Request;
///# use serde_json::json;
/// let post_request = Request::post("https://my.api/v1/resource")
/// .header("Content-Type", "application/json")
/// .body(Json(&json!({"foo": "bar"})))
/// .expect("Failed to build request.");
///
/// let get_request = Request::get("https://my.api/v1/resource")
/// .body(Nothing)
/// .expect("Failed to build request.");
/// ```
///
/// The callback function can build a loop message by passing or analyzing the
/// response body and metadata.
///
/// ```
///# use yew::{Component, ComponentLink, Html};
///# use yew_services::FetchService;
///# use yew_services::fetch::{Response, Request};
///# struct Comp;
///# impl Component for Comp {
///# type Message = Msg;type Properties = ();
///# fn create(props: Self::Properties,link: ComponentLink<Self>) -> Self {unimplemented!()}
///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()}
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
///# fn view(&self) -> Html {unimplemented!()}
///# }
///# enum Msg {
///# Noop,
///# Error
///# }
///# fn dont_execute() {
///# let link: ComponentLink<Comp> = unimplemented!();
///# let post_request: Request<Result<String, anyhow::Error>> = unimplemented!();
/// let task = FetchService::fetch(
/// post_request,
/// link.callback(|response: Response<Result<String, anyhow::Error>>| {
/// if response.status().is_success() {
/// Msg::Noop
/// } else {
/// Msg::Error
/// }
/// }),
/// );
///# }
/// ```
///
/// For a full example, you can specify that the response must be in the JSON format,
/// and be a specific serialized data type. If the mesage isn't Json, or isn't the specified
/// data type, then you will get a message indicating failure.
///
/// ```
///# use yew::format::{Json, Nothing, Format};
///# use yew_services::FetchService;
///# use http::Request;
///# use yew_services::fetch::Response;
///# use yew::{Component, ComponentLink, Html};
///# use serde_derive::Deserialize;
///# struct Comp;
///# impl Component for Comp {
///# type Message = Msg;type Properties = ();
///# fn create(props: Self::Properties,link: ComponentLink<Self>) -> Self {unimplemented!()}
///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()}
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
///# fn view(&self) -> Html {unimplemented!()}
///# }
///# enum Msg {
///# FetchResourceComplete(Data),
///# FetchResourceFailed
///# }
/// #[derive(Deserialize)]
/// struct Data {
/// value: String
/// }
///
///# fn dont_execute() {
///# let link: ComponentLink<Comp> = unimplemented!();
/// let get_request = Request::get("/thing").body(Nothing).unwrap();
/// let callback = link.callback(|response: Response<Json<Result<Data, anyhow::Error>>>| {
/// if let (meta, Json(Ok(body))) = response.into_parts() {
/// if meta.status.is_success() {
/// return Msg::FetchResourceComplete(body);
/// }
/// }
/// Msg::FetchResourceFailed
/// });
///
/// let task = FetchService::fetch(get_request, callback);
///# }
/// ```
///
pub fn fetch<IN, OUT: 'static>(
request: Request<IN>,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, &'static str>
where
IN: Into<Text>,
OUT: From<Text>,
{
fetch_impl::<IN, OUT, String, String>(false, request, None, callback)
}
/// `fetch` with provided `FetchOptions` object.
/// Use it if you need to send cookies with a request:
/// ```
///# use yew::format::Nothing;
///# use yew_services::fetch::{self, FetchOptions, Credentials};
///# use yew::{Html, Component, ComponentLink};
///# use yew_services::FetchService;
///# use http::Response;
///# struct Comp;
///# impl Component for Comp {
///# type Message = Msg;
///# type Properties = ();
///# fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {unimplemented!()}
///# fn update(&mut self, msg: Self::Message) -> bool {unimplemented!()}
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
///# fn view(&self) -> Html {unimplemented!()}
///# }
///# pub enum Msg { }
///# fn dont_execute() {
///# let link: ComponentLink<Comp> = unimplemented!();
///# let callback = link.callback(|response: Response<Result<String, anyhow::Error>>| -> Msg { unimplemented!() });
/// let request = fetch::Request::get("/path/")
/// .body(Nothing)
/// .unwrap();
/// let options = FetchOptions {
/// credentials: Some(Credentials::SameOrigin),
/// ..FetchOptions::default()
/// };
/// let task = FetchService::fetch_with_options(request, options, callback);
///# }
/// ```
pub fn fetch_with_options<IN, OUT: 'static>(
request: Request<IN>,
options: FetchOptions,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, &'static str>
where
IN: Into<Text>,
OUT: From<Text>,
{
fetch_impl::<IN, OUT, String, String>(false, request, Some(options), callback)
}
/// Fetch the data in binary format.
pub fn fetch_binary<IN, OUT: 'static>(
request: Request<IN>,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, &'static str>
where
IN: Into<Binary>,
OUT: From<Binary>,
{
fetch_impl::<IN, OUT, Vec<u8>, ArrayBuffer>(true, request, None, callback)
}
/// Fetch the data in binary format using the provided request options.
pub fn fetch_binary_with_options<IN, OUT: 'static>(
request: Request<IN>,
options: FetchOptions,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, &'static str>
where
IN: Into<Binary>,
OUT: From<Binary>,
{
fetch_impl::<IN, OUT, Vec<u8>, ArrayBuffer>(true, request, Some(options), callback)
}
}
fn fetch_impl<IN, OUT: 'static, T, X>(
binary: bool,
request: Request<IN>,
options: Option<FetchOptions>,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, &'static str>
where
IN: Into<Format<T>>,
OUT: From<Format<T>>,
T: JsSerialize,
X: TryFrom<Value> + Into<T>,
{
// Consume request as parts and body.
let (parts, body) = request.into_parts();
// Map headers into a Js `Header` to make sure it's supported.
let header_list = parts
.headers
.iter()
.map(|(k, v)| {
Ok((
k.as_str(),
v.to_str().map_err(|_| "Unparsable request header")?,
))
})
.collect::<Result<HashMap<_, _>, _>>()?;
let header_map = js! {
try {
return new Headers(@{header_list});
} catch(error) {
return error;
}
};
if Error::try_from(js!( return @{header_map.as_ref()}; )).is_ok() {
return Err("couldn't build headers");
}
// Formats URI.
let uri = parts.uri.to_string();
let method = parts.method.as_str();
let body = body.into().ok();
// Prepare the response callback.
// Notice that the callback signature must match the call from the JavaScript
// side. There is no static check at this point.
let callback = move |success: bool, status: u16, headers: HashMap<String, String>, data: X| {
let mut response_builder = Response::builder();
if let Ok(status) = StatusCode::from_u16(status) {
response_builder = response_builder.status(status);
}
for (key, values) in headers {
response_builder = response_builder.header(key.as_str(), values.as_str());
}
// Deserialize and wrap response data into a Text object.
let data = if success {
Ok(data.into())
} else {
Err(FetchError::FailedResponse.into())
};
let out = OUT::from(data);
let response = response_builder.body(out).unwrap();
callback.emit(response);
};
#[allow(clippy::too_many_arguments)]
let handle = js! {
var body = @{body};
if (@{binary} && body != null) {
body = Uint8Array.from(body);
}
var callback = @{callback};
var abortController = AbortController ? new AbortController() : null;
var handle = {
active: true,
callback,
abortController,
};
var init = {
method: @{method},
body: body,
headers: @{header_map},
};
var opts = @{Serde(options)} || {};
for (var attrname in opts) {
init[attrname] = opts[attrname];
}
if (abortController && !("signal" in init)) {
init.signal = abortController.signal;
}
fetch(@{uri}, init).then(function(response) {
var promise = (@{binary}) ? response.arrayBuffer() : response.text();
var status = response.status;
var headers = {};
response.headers.forEach(function(value, key) {
headers[key] = value;
});
promise.then(function(data) {
if (handle.active == true) {
handle.active = false;
callback(true, status, headers, data);
callback.drop();
}
}).catch(function(err) {
if (handle.active == true) {
handle.active = false;
callback(false, status, headers, data);
callback.drop();
}
});
}).catch(function(e) {
if (handle.active == true) {
var data = (@{binary}) ? new ArrayBuffer() : "";
handle.active = false;
callback(false, 408, {}, data);
callback.drop();
}
});
return handle;
};
Ok(FetchTask(Some(handle)))
}
impl Task for FetchTask {
fn is_active(&self) -> bool {
if let Some(ref task) = self.0 {
let result = js! {
var the_task = @{task};
return the_task.active &&
(!the_task.abortController || !the_task.abortController.signal.aborted);
};
result.try_into().unwrap_or(false)
} else {
false
}
}
}
impl Drop for FetchTask {
fn drop(&mut self) {
if self.is_active() {
// Fetch API doesn't support request cancelling in all browsers
// and we should use this workaround with a flag.
// In that case, request not canceled, but callback won't be called.
let handle = self
.0
.take()
.expect("tried to cancel request fetching twice");
js! { @(no_return)
var handle = @{handle};
handle.active = false;
handle.callback.drop();
if (handle.abortController) {
handle.abortController.abort();
}
}
}
}
}

View File

@ -1,572 +0,0 @@
//! `web-sys` implementation for the fetch service.
use super::Referrer;
use crate::Task;
use anyhow::{anyhow, Error};
use http::request::Parts;
use js_sys::{Array, Promise, Uint8Array};
use std::cell::RefCell;
use std::fmt;
use std::iter::FromIterator;
use std::marker::PhantomData;
use std::rc::Rc;
use thiserror::Error as ThisError;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::{spawn_local, JsFuture};
use web_sys::{
AbortController, Headers, ReferrerPolicy, Request as WebRequest, RequestInit,
Response as WebResponse,
};
use yew::callback::Callback;
use yew::format::{Binary, Format, Text};
#[doc(no_inline)]
pub use web_sys::{
RequestCache as Cache, RequestCredentials as Credentials, RequestMode as Mode,
RequestRedirect as Redirect, Window, WorkerGlobalScope,
};
#[doc(no_inline)]
pub use http::{HeaderMap, Method, Request, Response, StatusCode, Uri};
trait JsInterop: Sized {
fn from_js(js_value: JsValue) -> Result<Self, FetchError>;
fn to_js(self) -> JsValue;
}
impl JsInterop for Vec<u8> {
fn from_js(js_value: JsValue) -> Result<Self, FetchError> {
Ok(Uint8Array::new(&js_value).to_vec())
}
fn to_js(self) -> JsValue {
Uint8Array::from(self.as_slice()).into()
}
}
impl JsInterop for String {
fn from_js(js_value: JsValue) -> Result<Self, FetchError> {
js_value.as_string().ok_or(FetchError::InternalError)
}
fn to_js(self) -> JsValue {
self.into()
}
}
/// Init options for `fetch()` function call.
/// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
#[derive(Default, Debug)]
pub struct FetchOptions {
/// Cache of a fetch request.
pub cache: Option<Cache>,
/// Credentials of a fetch request.
pub credentials: Option<Credentials>,
/// Redirect behaviour of a fetch request.
pub redirect: Option<Redirect>,
/// Request mode of a fetch request.
pub mode: Option<Mode>,
/// Referrer of a fetch request.
pub referrer: Option<Referrer>,
/// Referrer policy of a fetch request.
pub referrer_policy: Option<ReferrerPolicy>,
/// Integrity of a fetch request.
pub integrity: Option<String>,
}
impl Into<RequestInit> for FetchOptions {
fn into(self) -> RequestInit {
let mut init = RequestInit::new();
if let Some(cache) = self.cache {
init.cache(cache);
}
if let Some(credentials) = self.credentials {
init.credentials(credentials);
}
if let Some(redirect) = self.redirect {
init.redirect(redirect);
}
if let Some(mode) = self.mode {
init.mode(mode);
}
if let Some(referrer) = self.referrer {
match referrer {
Referrer::SameOriginUrl(referrer) => init.referrer(&referrer),
Referrer::AboutClient => init.referrer("about:client"),
Referrer::Empty => init.referrer(""),
};
}
if let Some(referrer_policy) = self.referrer_policy {
init.referrer_policy(referrer_policy);
}
if let Some(integrity) = self.integrity {
init.integrity(&integrity);
}
init
}
}
// convert `headers` to `Iterator<Item = (String, String)>`
fn header_iter(headers: Headers) -> impl Iterator<Item = (String, String)> {
js_sys::try_iter(&headers)
.unwrap()
.unwrap()
.map(Result::unwrap)
.map(|entry| {
let entry = Array::from(&entry);
let key = entry.get(0);
let value = entry.get(1);
(key.as_string().unwrap(), value.as_string().unwrap())
})
}
/// Represents errors of a fetch service.
#[derive(Debug, ThisError)]
enum FetchError {
#[error("canceled")]
Canceled,
#[error("{0}")]
FetchFailed(String),
#[error("invalid response")]
InvalidResponse,
#[error("unexpected error, please report")]
InternalError,
}
#[derive(Debug)]
struct Handle {
active: Rc<RefCell<bool>>,
abort_controller: Option<AbortController>,
}
/// A handle to control sent requests.
#[must_use = "the request will be cancelled when the task is dropped"]
pub struct FetchTask(Handle);
impl fmt::Debug for FetchTask {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("FetchTask")
}
}
/// A service to fetch resources.
#[derive(Default, Debug)]
pub struct FetchService {}
impl FetchService {
/// Sends a request to a remote server given a Request object and a callback
/// function to convert a Response object into a loop's message.
///
/// You may use a Request builder to build your request declaratively as on the
/// following examples:
///
/// ```
///# use yew::format::{Nothing, Json};
///# use yew_services::fetch::Request;
///# use serde_json::json;
/// let post_request = Request::post("https://my.api/v1/resource")
/// .header("Content-Type", "application/json")
/// .body(Json(&json!({"foo": "bar"})))
/// .expect("Failed to build request.");
///
/// let get_request = Request::get("https://my.api/v1/resource")
/// .body(Nothing)
/// .expect("Failed to build request.");
/// ```
///
/// The callback function can build a loop message by passing or analyzing the
/// response body and metadata.
///
/// ```
///# use yew::{Component, ComponentLink, Html};
///# use yew_services::FetchService;
///# use yew_services::fetch::{Response, Request};
///# use anyhow::Error;
///# struct Comp;
///# impl Component for Comp {
///# type Message = Msg;type Properties = ();
///# fn create(props: Self::Properties,link: ComponentLink<Self>) -> Self {unimplemented!()}
///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()}
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
///# fn view(&self) -> Html {unimplemented!()}
///# }
///# enum Msg {
///# Noop,
///# Error
///# }
///# fn dont_execute() {
///# let link: ComponentLink<Comp> = unimplemented!();
///# let post_request: Request<Result<String, Error>> = unimplemented!();
/// let task = FetchService::fetch(
/// post_request,
/// link.callback(|response: Response<Result<String, Error>>| {
/// if response.status().is_success() {
/// Msg::Noop
/// } else {
/// Msg::Error
/// }
/// }),
/// );
///# }
/// ```
///
/// For a full example, you can specify that the response must be in the JSON format,
/// and be a specific serialized data type. If the message isn't JSON, or isn't the specified
/// data type, then you will get an error message.
///
/// ```
///# use yew::format::{Json, Nothing, Format};
///# use yew_services::FetchService;
///# use http::Request;
///# use yew_services::fetch::Response;
///# use yew::{Component, ComponentLink, Html};
///# use serde::Deserialize;
///# use anyhow::Error;
///# struct Comp;
///# impl Component for Comp {
///# type Message = Msg;type Properties = ();
///# fn create(props: Self::Properties,link: ComponentLink<Self>) -> Self {unimplemented!()}
///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()}
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
///# fn view(&self) -> Html {unimplemented!()}
///# }
///# enum Msg {
///# FetchResourceComplete(Data),
///# FetchResourceFailed
///# }
/// #[derive(Deserialize)]
/// struct Data {
/// value: String
/// }
///
///# fn dont_execute() {
///# let link: ComponentLink<Comp> = unimplemented!();
/// let get_request = Request::get("/thing").body(Nothing).unwrap();
/// let callback = link.callback(|response: Response<Json<Result<Data, Error>>>| {
/// if let (meta, Json(Ok(body))) = response.into_parts() {
/// if meta.status.is_success() {
/// return Msg::FetchResourceComplete(body);
/// }
/// }
/// Msg::FetchResourceFailed
/// });
///
/// let task = FetchService::fetch(get_request, callback);
///# }
/// ```
///
pub fn fetch<IN, OUT: 'static>(
request: Request<IN>,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, Error>
where
IN: Into<Text>,
OUT: From<Text>,
{
fetch_impl::<IN, OUT, String>(false, request, None, callback)
}
/// `fetch` with provided `FetchOptions` object.
/// Use it if you need to send cookies with a request:
/// ```
///# use yew::format::Nothing;
///# use yew_services::fetch::{self, FetchOptions, Credentials};
///# use yew::{Html, Component, ComponentLink};
///# use yew_services::FetchService;
///# use http::Response;
///# use anyhow::Error;
///# struct Comp;
///# impl Component for Comp {
///# type Message = Msg;
///# type Properties = ();
///# fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {unimplemented!()}
///# fn update(&mut self, msg: Self::Message) -> bool {unimplemented!()}
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
///# fn view(&self) -> Html {unimplemented!()}
///# }
///# pub enum Msg {}
///# fn dont_execute() {
///# let link: ComponentLink<Comp> = unimplemented!();
///# let callback = link.callback(|response: Response<Result<String, Error>>| -> Msg { unimplemented!() });
/// let request = fetch::Request::get("/path/")
/// .body(Nothing)
/// .unwrap();
/// let options = FetchOptions {
/// credentials: Some(Credentials::SameOrigin),
/// ..FetchOptions::default()
/// };
/// let task = FetchService::fetch_with_options(request, options, callback);
///# }
/// ```
pub fn fetch_with_options<IN, OUT: 'static>(
request: Request<IN>,
options: FetchOptions,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, Error>
where
IN: Into<Text>,
OUT: From<Text>,
{
fetch_impl::<IN, OUT, String>(false, request, Some(options), callback)
}
/// Fetch the data in binary format.
pub fn fetch_binary<IN, OUT: 'static>(
request: Request<IN>,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, Error>
where
IN: Into<Binary>,
OUT: From<Binary>,
{
fetch_impl::<IN, OUT, Vec<u8>>(true, request, None, callback)
}
/// Fetch the data in binary format with the provided request options.
pub fn fetch_binary_with_options<IN, OUT: 'static>(
request: Request<IN>,
options: FetchOptions,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, Error>
where
IN: Into<Binary>,
OUT: From<Binary>,
{
fetch_impl::<IN, OUT, Vec<u8>>(true, request, Some(options), callback)
}
}
fn fetch_impl<IN, OUT: 'static, DATA: 'static>(
binary: bool,
request: Request<IN>,
options: Option<FetchOptions>,
callback: Callback<Response<OUT>>,
) -> Result<FetchTask, Error>
where
DATA: JsInterop,
IN: Into<Format<DATA>>,
OUT: From<Format<DATA>>,
{
// Transform http::Request into WebRequest.
let (parts, body) = request.into_parts();
let body = match body.into() {
Ok(b) => b.to_js(),
Err(_) => JsValue::NULL,
};
let request = build_request(parts, &body)?;
// Transform FetchOptions into RequestInit.
let abort_controller = AbortController::new().ok();
let mut init = options.map_or_else(RequestInit::new, Into::into);
if let Some(abort_controller) = &abort_controller {
init.signal(Some(&abort_controller.signal()));
}
// Start fetch
let promise = GLOBAL.with(|global| global.fetch_with_request_and_init(&request, &init));
// Spawn future to resolve fetch
let active = Rc::new(RefCell::new(true));
let data_fetcher = DataFetcher::new(binary, callback, active.clone());
spawn_local(DataFetcher::fetch_data(data_fetcher, promise));
Ok(FetchTask(Handle {
active,
abort_controller,
}))
}
struct DataFetcher<OUT: 'static, DATA>
where
DATA: JsInterop,
OUT: From<Format<DATA>>,
{
binary: bool,
active: Rc<RefCell<bool>>,
callback: Callback<Response<OUT>>,
_marker: PhantomData<DATA>,
}
impl<OUT: 'static, DATA> DataFetcher<OUT, DATA>
where
DATA: JsInterop,
OUT: From<Format<DATA>>,
{
fn new(binary: bool, callback: Callback<Response<OUT>>, active: Rc<RefCell<bool>>) -> Self {
Self {
binary,
callback,
active,
_marker: PhantomData::default(),
}
}
async fn fetch_data(self, promise: Promise) {
let result = self.fetch_data_impl(promise).await;
let (data, status, headers) = match result {
Ok((data, response)) => (Ok(data), response.status(), Some(response.headers())),
Err(err) => (Err(err), 408, None),
};
self.callback(data, status, headers);
}
async fn fetch_data_impl(&self, promise: Promise) -> Result<(DATA, WebResponse), Error> {
let response = self.get_response(promise).await?;
let data = self.get_data(&response).await?;
Ok((data, response))
}
// Prepare the response callback.
// Notice that the callback signature must match the call from the javascript
// side. There is no static check at this point.
fn callback(&self, data: Result<DATA, Error>, status: u16, headers: Option<Headers>) {
let mut response_builder = Response::builder();
if let Ok(status) = StatusCode::from_u16(status) {
response_builder = response_builder.status(status);
}
if let Some(headers) = headers {
for (key, value) in header_iter(headers) {
response_builder = response_builder.header(key.as_str(), value.as_str());
}
}
// Deserialize and wrap response data into a Text or Binary object.
let response = response_builder
.body(OUT::from(data))
.expect("failed to build response, please report");
*self.active.borrow_mut() = false;
self.callback.emit(response);
}
async fn get_response(&self, fetch_promise: Promise) -> Result<WebResponse, FetchError> {
let response = JsFuture::from(fetch_promise)
.await
.map_err(|err| err.unchecked_into::<js_sys::Error>())
.map_err(|err| FetchError::FetchFailed(err.to_string().as_string().unwrap()))?;
if *self.active.borrow() {
Ok(WebResponse::from(response))
} else {
Err(FetchError::Canceled)
}
}
async fn get_data(&self, response: &WebResponse) -> Result<DATA, FetchError> {
let data_promise = if self.binary {
response.array_buffer()
} else {
response.text()
}
.map_err(|_| FetchError::InvalidResponse)?;
let data_result = JsFuture::from(data_promise).await;
if *self.active.borrow() {
data_result
.map_err(|_| FetchError::InvalidResponse)
.and_then(DATA::from_js)
} else {
Err(FetchError::Canceled)
}
}
}
fn build_request(parts: Parts, body: &JsValue) -> Result<WebRequest, Error> {
// Map headers into a Js `Header` type.
let header_list = parts
.headers
.iter()
.map(|(k, v)| {
Ok(Array::from_iter(&[
JsValue::from_str(k.as_str()),
JsValue::from_str(
v.to_str()
.map_err(|_| anyhow!("Unparsable request header"))?,
),
]))
})
.collect::<Result<Array, Error>>()?;
let header_map = Headers::new_with_str_sequence_sequence(&header_list)
.map_err(|_| anyhow!("couldn't build headers"))?;
// Formats URI.
let uri = parts.uri.to_string();
let method = parts.method.as_str();
let mut init = RequestInit::new();
init.method(method).body(Some(body)).headers(&header_map);
WebRequest::new_with_str_and_init(&uri, &init).map_err(|_| anyhow!("failed to build request"))
}
impl Task for FetchTask {
fn is_active(&self) -> bool {
*self.0.active.borrow()
}
}
impl Drop for FetchTask {
fn drop(&mut self) {
if self.is_active() {
// Fetch API doesn't support request cancelling in all browsers
// and we should use this workaround with a flag.
// In that case, request not canceled, but callback won't be called.
*self.0.active.borrow_mut() = false;
if let Some(abort_controller) = &self.0.abort_controller {
abort_controller.abort();
}
}
}
}
thread_local! {
static GLOBAL: WindowOrWorker = WindowOrWorker::new();
}
enum WindowOrWorker {
Window(Window),
Worker(WorkerGlobalScope),
}
impl WindowOrWorker {
fn new() -> Self {
#[wasm_bindgen]
extern "C" {
type Global;
#[wasm_bindgen(method, getter, js_name = Window)]
fn window(this: &Global) -> JsValue;
#[wasm_bindgen(method, getter, js_name = WorkerGlobalScope)]
fn worker(this: &Global) -> JsValue;
}
let global: Global = js_sys::global().unchecked_into();
if !global.window().is_undefined() {
Self::Window(global.unchecked_into())
} else if !global.worker().is_undefined() {
Self::Worker(global.unchecked_into())
} else {
panic!(
"Yew's `FetchService` only works when a `window` or `worker` object is available."
);
}
}
}
impl WindowOrWorker {
fn fetch_with_request_and_init(&self, input: &WebRequest, init: &RequestInit) -> Promise {
match self {
Self::Window(window) => window.fetch_with_request_and_init(input, init),
Self::Worker(worker) => worker.fetch_with_request_and_init(input, init),
}
}
}

View File

@ -2,29 +2,16 @@
//! periodic sending messages to a loop.
use super::Task;
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use gloo::timers::callback::Interval;
use std::convert::TryInto;
use std::fmt;
use std::time::Duration;
use yew::callback::Callback;
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 gloo::timers::callback::Interval;
}
}
/// A handle which helps to cancel interval. Uses
/// [clearInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearInterval).
#[must_use = "the interval is only active until the handle is dropped"]
pub struct IntervalTask(
#[cfg(feature = "std_web")] Value,
#[cfg(feature = "web_sys")] Interval,
);
pub struct IntervalTask(Interval);
impl fmt::Debug for IntervalTask {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -51,20 +38,7 @@ impl IntervalService {
.as_millis()
.try_into()
.expect("duration doesn't fit in u32");
let handle = cfg_match! {
feature = "std_web" => js! {
var callback = @{callback};
var action = function() {
callback();
};
var delay = @{ms};
return {
interval_id: setInterval(action, delay),
callback: callback,
};
},
feature = "web_sys" => Interval::new(ms, callback),
};
let handle = Interval::new(ms, callback);
IntervalTask(handle)
}
}
@ -74,19 +48,3 @@ impl Task for IntervalTask {
true
}
}
impl Drop for IntervalTask {
fn drop(&mut self) {
#[cfg(feature = "std_web")]
{
if self.is_active() {
let handle = &self.0;
js! { @(no_return)
var handle = @{handle};
clearInterval(handle.interval_id);
handle.callback.drop();
}
}
}
}
}

View File

@ -1,19 +1,10 @@
//! Service to register key press event listeners on elements.
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use gloo::events::{EventListener, EventListenerOptions};
use std::fmt;
use wasm_bindgen::JsCast;
use web_sys::{Event, EventTarget, KeyboardEvent};
use yew::callback::Callback;
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::web::event::{ConcreteEvent, KeyDownEvent, KeyPressEvent, KeyUpEvent};
use stdweb::web::{EventListenerHandle, IEventTarget};
} else if #[cfg(feature = "web_sys")] {
use gloo::events::{EventListener, EventListenerOptions};
use wasm_bindgen::JsCast;
use web_sys::{Event, EventTarget, KeyboardEvent};
}
}
/// Service for registering callbacks on elements to get keystrokes from the user.
///
@ -31,10 +22,7 @@ pub struct KeyboardService {}
///
/// When the handle goes out of scope, the listener will be removed from the element.
#[must_use = "the listener is only active until the handle is dropped"]
pub struct KeyListenerHandle(
#[cfg(feature = "std_web")] Option<EventListenerHandle>,
#[cfg(feature = "web_sys")] EventListener,
);
pub struct KeyListenerHandle(EventListener);
impl fmt::Debug for KeyListenerHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -51,18 +39,11 @@ impl KeyboardService {
/// # Warning
/// This API has been deprecated in the HTML standard and it is not recommended for use in new projects.
/// Consult the browser compatibility chart in the linked MDN documentation.
pub fn register_key_press<
#[cfg(feature = "std_web")] T: IEventTarget,
#[cfg(feature = "web_sys")] T: AsRef<EventTarget>,
>(
pub fn register_key_press<T: AsRef<EventTarget>>(
element: &T,
#[cfg(feature = "std_web")] callback: Callback<KeyPressEvent>,
#[cfg(feature = "web_sys")] callback: Callback<KeyboardEvent>,
callback: Callback<KeyboardEvent>,
) -> KeyListenerHandle {
cfg_match! {
feature = "std_web" => register_key_impl(element, callback),
feature = "web_sys" => register_key_impl(element, callback, "keypress"),
}
register_key_impl(element, callback, "keypress")
}
/// Registers a callback which listens to KeyDownEvents on a provided element.
@ -74,18 +55,11 @@ impl KeyboardService {
/// This browser feature is relatively new and is set to replace the `keypress` event.
/// It may not be fully supported in all browsers.
/// Consult the browser compatibility chart in the linked MDN documentation.
pub fn register_key_down<
#[cfg(feature = "std_web")] T: IEventTarget,
#[cfg(feature = "web_sys")] T: AsRef<EventTarget>,
>(
pub fn register_key_down<T: AsRef<EventTarget>>(
element: &T,
#[cfg(feature = "std_web")] callback: Callback<KeyDownEvent>,
#[cfg(feature = "web_sys")] callback: Callback<KeyboardEvent>,
callback: Callback<KeyboardEvent>,
) -> KeyListenerHandle {
cfg_match! {
feature = "std_web" => register_key_impl(element, callback),
feature = "web_sys" => register_key_impl(element, callback, "keydown"),
}
register_key_impl(element, callback, "keydown")
}
/// Registers a callback that listens to KeyUpEvents on a provided element.
@ -97,36 +71,14 @@ impl KeyboardService {
/// This browser feature is relatively new and is set to replace keypress events.
/// It may not be fully supported in all browsers.
/// Consult the browser compatibility chart in the linked MDN documentation.
pub fn register_key_up<
#[cfg(feature = "std_web")] T: IEventTarget,
#[cfg(feature = "web_sys")] T: AsRef<EventTarget>,
>(
pub fn register_key_up<T: AsRef<EventTarget>>(
element: &T,
#[cfg(feature = "std_web")] callback: Callback<KeyUpEvent>,
#[cfg(feature = "web_sys")] callback: Callback<KeyboardEvent>,
callback: Callback<KeyboardEvent>,
) -> KeyListenerHandle {
cfg_match! {
feature = "std_web" => register_key_impl(element, callback),
feature = "web_sys" => register_key_impl(element, callback, "keyup"),
}
register_key_impl(element, callback, "keyup")
}
}
#[cfg(feature = "std_web")]
fn register_key_impl<T: IEventTarget, E: 'static + ConcreteEvent>(
element: &T,
callback: Callback<E>,
) -> KeyListenerHandle {
let handle = element.add_event_listener(move |event: E| {
callback.emit(event);
});
cfg_match! {
feature = "std_web" => KeyListenerHandle(Some(handle)),
feature = "web_sys" => KeyListenerHandle(handle),
}
}
#[cfg(feature = "web_sys")]
fn register_key_impl<T: AsRef<EventTarget>>(
element: &T,
callback: Callback<KeyboardEvent>,
@ -147,14 +99,3 @@ fn register_key_impl<T: AsRef<EventTarget>>(
listener,
))
}
#[cfg(feature = "std_web")]
impl Drop for KeyListenerHandle {
fn drop(&mut self) {
if let Some(handle) = self.0.take() {
handle.remove()
} else {
panic!("Tried to drop KeyListenerHandle twice")
}
}
}

View File

@ -1,16 +1,15 @@
//! Service to load files using `FileReader`.
use crate::Task;
use anyhow::{anyhow, Result};
use gloo::events::EventListener;
use js_sys::Uint8Array;
use std::cmp;
use std::fmt;
cfg_if::cfg_if! {
if #[cfg(feature = "std_web")] {
mod std_web;
pub use std_web::*;
} else if #[cfg(feature = "web_sys")] {
mod web_sys;
pub use self::web_sys::*;
}
}
#[doc(no_inline)]
pub use web_sys::{Blob, File};
use web_sys::{Event, FileReader};
use yew::callback::Callback;
/// Struct that represents data of a file.
#[derive(Clone, Debug)]
@ -44,6 +43,95 @@ pub enum FileChunk {
#[derive(Default, Debug)]
pub struct ReaderService {}
impl ReaderService {
/// Reads all bytes from a file and returns them with a callback.
pub fn read_file(file: File, callback: Callback<FileData>) -> Result<ReaderTask> {
let file_reader = FileReader::new().map_err(|_| anyhow!("couldn't acquire file reader"))?;
let reader = file_reader.clone();
let name = file.name();
let callback = move |_event: &Event| {
if let Ok(result) = reader.result() {
let array = Uint8Array::new(&result);
let data = FileData {
name,
content: array.to_vec(),
};
callback.emit(data);
}
};
let listener = EventListener::once(&file_reader, "loadend", callback);
file_reader.read_as_array_buffer(&file).unwrap();
Ok(ReaderTask {
file_reader,
listener,
})
}
/// Reads data chunks from a file and returns them with a callback.
pub fn read_file_by_chunks(
file: File,
callback: Callback<Option<FileChunk>>,
chunk_size: usize,
) -> Result<ReaderTask> {
let file_reader = FileReader::new().map_err(|_| anyhow!("couldn't aquire file reader"))?;
let name = file.name();
let mut position = 0;
let total_size = file.size() as usize;
let reader = file_reader.clone();
let callback = move |_event: &Event| {
if let Ok(result) = reader.result() {
if result.is_string() {
let started = FileChunk::Started { name: name.clone() };
callback.emit(Some(started));
} else {
let array = Uint8Array::new_with_byte_offset(&result, 0);
let chunk = FileChunk::DataChunk {
data: array.to_vec(),
progress: position as f32 / total_size as f32,
};
callback.emit(Some(chunk));
};
// Read the next chunk
if position < total_size {
let from = position;
let to = cmp::min(position + chunk_size, total_size);
position = to;
let blob = file.slice_with_i32_and_i32(from as _, to as _).unwrap();
if let Err(..) = reader.read_as_array_buffer(&blob) {
callback.emit(None);
}
} else {
let finished = FileChunk::Finished;
callback.emit(Some(finished));
}
} else {
callback.emit(None);
}
};
let listener = EventListener::new(&file_reader, "loadend", callback);
let blob = Blob::new().map_err(|_| anyhow!("Blob constructor is not supported"))?;
file_reader.read_as_text(&blob).unwrap();
Ok(ReaderTask {
file_reader,
listener,
})
}
}
/// A handle to control reading.
#[must_use = "the reader will abort when the task is dropped"]
pub struct ReaderTask {
pub(super) file_reader: FileReader,
#[allow(dead_code)]
listener: EventListener,
}
impl Task for ReaderTask {
fn is_active(&self) -> bool {
self.file_reader.ready_state() == FileReader::LOADING
}
}
impl fmt::Debug for ReaderTask {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ReaderTask")

View File

@ -1,122 +0,0 @@
//! `stdweb` implementation for the reader service.
use super::*;
use crate::Task;
use std::cmp;
use stdweb::unstable::{TryFrom, TryInto};
use stdweb::web::event::LoadEndEvent;
#[doc(no_inline)]
pub use stdweb::web::{Blob, File, IBlob};
use stdweb::web::{FileReader, FileReaderReadyState, FileReaderResult, IEventTarget, TypedArray};
#[allow(unused_imports)]
use stdweb::{_js_impl, js};
use yew::callback::Callback;
fn new_file_reader() -> Result<FileReader, &'static str> {
let file_reader = js! {
try {
return new FileReader;
} catch(error) {
return error;
}
};
FileReader::try_from(js!( return @{file_reader.as_ref()}; ))
.map_err(|_| "couldn't aquire file reader")
}
impl ReaderService {
/// Reads all bytes from a file and returns them with a callback.
pub fn read_file(file: File, callback: Callback<FileData>) -> Result<ReaderTask, &'static str> {
let file_reader = new_file_reader()?;
let reader = file_reader.clone();
let name = file.name();
file_reader.add_event_listener(move |_event: LoadEndEvent| match reader.result() {
Some(FileReaderResult::String(_)) => {
unreachable!();
}
Some(FileReaderResult::ArrayBuffer(buffer)) => {
let array: TypedArray<u8> = buffer.into();
let data = FileData {
name: name.clone(),
content: array.to_vec(),
};
callback.emit(data);
}
None => {}
});
file_reader.read_as_array_buffer(&file).unwrap();
Ok(ReaderTask { file_reader })
}
/// Reads data chunks from a file and returns them with a callback.
pub fn read_file_by_chunks(
file: File,
callback: Callback<Option<FileChunk>>,
chunk_size: usize,
) -> Result<ReaderTask, &'static str> {
let file_reader = new_file_reader()?;
let name = file.name();
let mut position = 0;
let total_size = file.len() as usize;
let reader = file_reader.clone();
file_reader.add_event_listener(move |_event: LoadEndEvent| {
if let Some(result) = reader.result() {
match result {
// This branch is used to start reading
FileReaderResult::String(_) => {
let started = FileChunk::Started { name: name.clone() };
callback.emit(Some(started));
}
// This branch is used to send a chunk value
FileReaderResult::ArrayBuffer(buffer) => {
let array: TypedArray<u8> = buffer.into();
let chunk = FileChunk::DataChunk {
data: array.to_vec(),
progress: position as f32 / total_size as f32,
};
callback.emit(Some(chunk));
}
}
// Read the next chunk
if position < total_size {
let file = &file;
let from = position;
let to = cmp::min(position + chunk_size, total_size);
position = to;
// TODO(#942): Implement `slice` method in `stdweb`
let blob: Blob = (js! {
return @{file}.slice(@{from as u32}, @{to as u32});
})
.try_into()
.unwrap();
reader.read_as_array_buffer(&blob).unwrap();
} else {
let finished = FileChunk::Finished;
callback.emit(Some(finished));
}
} else {
callback.emit(None);
}
});
let blob: Blob = (js! {
return (new Blob());
})
.try_into()
.unwrap();
file_reader.read_as_text(&blob).unwrap();
Ok(ReaderTask { file_reader })
}
}
/// A handle to control reading.
#[must_use = "the reader will abort when the task is dropped"]
pub struct ReaderTask {
pub(super) file_reader: FileReader,
}
impl Task for ReaderTask {
fn is_active(&self) -> bool {
self.file_reader.ready_state() == FileReaderReadyState::Loading
}
}

View File

@ -1,101 +0,0 @@
//! `web-sys` implementation for the reader service.
use super::*;
use crate::Task;
#[doc(no_inline)]
pub use ::web_sys::{Blob, File};
use ::web_sys::{Event, FileReader};
use anyhow::{anyhow, Result};
use gloo::events::EventListener;
use js_sys::Uint8Array;
use std::cmp;
use yew::callback::Callback;
impl ReaderService {
/// Reads all bytes from a file and returns them with a callback.
pub fn read_file(file: File, callback: Callback<FileData>) -> Result<ReaderTask> {
let file_reader = FileReader::new().map_err(|_| anyhow!("couldn't acquire file reader"))?;
let reader = file_reader.clone();
let name = file.name();
let callback = move |_event: &Event| {
if let Ok(result) = reader.result() {
let array = Uint8Array::new(&result);
let data = FileData {
name,
content: array.to_vec(),
};
callback.emit(data);
}
};
let listener = EventListener::once(&file_reader, "loadend", callback);
file_reader.read_as_array_buffer(&file).unwrap();
Ok(ReaderTask {
file_reader,
listener,
})
}
/// Reads data chunks from a file and returns them with a callback.
pub fn read_file_by_chunks(
file: File,
callback: Callback<Option<FileChunk>>,
chunk_size: usize,
) -> Result<ReaderTask> {
let file_reader = FileReader::new().map_err(|_| anyhow!("couldn't aquire file reader"))?;
let name = file.name();
let mut position = 0;
let total_size = file.size() as usize;
let reader = file_reader.clone();
let callback = move |_event: &Event| {
if let Ok(result) = reader.result() {
if result.is_string() {
let started = FileChunk::Started { name: name.clone() };
callback.emit(Some(started));
} else {
let array = Uint8Array::new_with_byte_offset(&result, 0);
let chunk = FileChunk::DataChunk {
data: array.to_vec(),
progress: position as f32 / total_size as f32,
};
callback.emit(Some(chunk));
};
// Read the next chunk
if position < total_size {
let from = position;
let to = cmp::min(position + chunk_size, total_size);
position = to;
let blob = file.slice_with_i32_and_i32(from as _, to as _).unwrap();
if let Err(..) = reader.read_as_array_buffer(&blob) {
callback.emit(None);
}
} else {
let finished = FileChunk::Finished;
callback.emit(Some(finished));
}
} else {
callback.emit(None);
}
};
let listener = EventListener::new(&file_reader, "loadend", callback);
let blob = Blob::new().map_err(|_| anyhow!("Blob constructor is not supported"))?;
file_reader.read_as_text(&blob).unwrap();
Ok(ReaderTask {
file_reader,
listener,
})
}
}
/// A handle to control reading.
#[must_use = "the reader will abort when the task is dropped"]
pub struct ReaderTask {
pub(super) file_reader: FileReader,
#[allow(dead_code)]
listener: EventListener,
}
impl Task for ReaderTask {
fn is_active(&self) -> bool {
self.file_reader.ready_state() == FileReader::LOADING
}
}

View File

@ -2,31 +2,16 @@
//! request frame rendering
use crate::Task;
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use std::fmt;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::{JsCast, JsValue};
use yew::callback::Callback;
cfg_if! {
if #[cfg(feature = "std_web")] {
#[allow(unused_imports)]
use stdweb::{_js_impl, js};
use stdweb::unstable::TryInto;
use stdweb::Value;
} else if #[cfg(feature = "web_sys")] {
use yew::utils;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::{JsCast, JsValue};
}
}
use yew::utils;
/// A handle to cancel a render task.
#[must_use = "the task will be cancelled when the task is dropped"]
pub struct RenderTask(
#[cfg(feature = "std_web")] Value,
#[cfg(feature = "web_sys")] RenderTaskInner,
);
pub struct RenderTask(RenderTaskInner);
#[cfg(feature = "web_sys")]
struct RenderTaskInner {
render_id: i32,
#[allow(dead_code)]
@ -46,39 +31,19 @@ pub struct RenderService {}
impl RenderService {
/// Request animation frame. Callback will be notified when frame should be rendered.
pub fn request_animation_frame(callback: Callback<f64>) -> RenderTask {
let callback = move |#[cfg(feature = "std_web")] v,
#[cfg(feature = "web_sys")] v: JsValue| {
let time: f64 = cfg_match! {
feature = "std_web" => ({
match v {
Value::Number(n) => n.try_into().unwrap(),
_ => 0.0,
}
}),
feature = "web_sys" => v.as_f64().unwrap_or(0.),
};
let callback = move |v: JsValue| {
let time: f64 = v.as_f64().unwrap_or(0.);
callback.emit(time);
};
let handle = cfg_match! {
feature = "std_web" => js! {
var callback = @{callback};
var action = function(time) {
callback(time);
callback.drop();
};
return {
render_id: requestAnimationFrame(action),
callback: callback,
};
},
feature = "web_sys" => ({
let callback = Closure::wrap(Box::new(callback) as Box<dyn Fn(JsValue)>);
let render_id = utils::window().request_animation_frame(callback.as_ref().unchecked_ref()).unwrap();
RenderTaskInner {
render_id,
callback,
}
}),
let handle = {
let callback = Closure::wrap(Box::new(callback) as Box<dyn Fn(JsValue)>);
let render_id = utils::window()
.request_animation_frame(callback.as_ref().unchecked_ref())
.unwrap();
RenderTaskInner {
render_id,
callback,
}
};
RenderTask(handle)
}
@ -93,17 +58,9 @@ impl Task for RenderTask {
impl Drop for RenderTask {
fn drop(&mut self) {
if self.is_active() {
cfg_match! {
feature = "std_web" => ({
let handle = &self.0;
js! { @(no_return)
var handle = @{handle};
cancelAnimationFrame(handle.render_id);
handle.callback.drop();
}
}),
feature = "web_sys" => utils::window().cancel_animation_frame(self.0.render_id).unwrap(),
}
utils::window()
.cancel_animation_frame(self.0.render_id)
.unwrap()
}
}
}

View File

@ -1,19 +1,9 @@
//! This module contains Yew's implementation of a service which listens for browser window resize events.
use crate::Task;
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use gloo::events::EventListener;
use std::fmt;
use web_sys::{Event, Window};
use yew::callback::Callback;
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::js;
use stdweb::web::{event::ResizeEvent, window, Window};
use stdweb::Value;
} else if #[cfg(feature = "web_sys")] {
use gloo::events::EventListener;
use web_sys::{Event, Window};
}
}
/// A service which fires events when the browser window is resized.
#[derive(Default, Debug)]
@ -21,10 +11,7 @@ pub struct ResizeService {}
/// A handle for the event listener listening for resize events.
#[must_use = "the listener is only active until the task is dropped"]
pub struct ResizeTask(
#[cfg(feature = "std_web")] Value,
#[cfg(feature = "web_sys")] EventListener,
);
pub struct ResizeTask(EventListener);
impl fmt::Debug for ResizeTask {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -46,7 +33,6 @@ impl WindowDimensions {
pub fn get_dimensions(window: &Window) -> Self {
let width = window.inner_width();
let height = window.inner_height();
#[cfg(feature = "web_sys")]
let (width, height) = {
(
width.unwrap().as_f64().unwrap() as _,
@ -60,24 +46,12 @@ impl WindowDimensions {
impl ResizeService {
/// Register a callback that will be called when the browser window resizes.
pub fn register(callback: Callback<WindowDimensions>) -> ResizeTask {
let callback =
move |#[cfg(feature = "web_sys")] _event: &Event,
#[cfg(feature = "std_web")] _event: ResizeEvent| {
let window = cfg_match! {
feature = "std_web" => window(),
feature = "web_sys" => web_sys::window().unwrap(),
};
let dimensions = WindowDimensions::get_dimensions(&window);
callback.emit(dimensions);
};
let handle = cfg_match! {
feature = "std_web" => js! {
var handle = @{callback};
window.addEventListener("resize", handle);
return handle;
},
feature = "web_sys" => EventListener::new(&web_sys::window().unwrap(), "resize", callback),
let callback = move |_event: &Event| {
let window = web_sys::window().unwrap();
let dimensions = WindowDimensions::get_dimensions(&window);
callback.emit(dimensions);
};
let handle = EventListener::new(&web_sys::window().unwrap(), "resize", callback);
ResizeTask(handle)
}
}
@ -87,17 +61,3 @@ impl Task for ResizeTask {
true
}
}
impl Drop for ResizeTask {
fn drop(&mut self) {
#[cfg(feature = "std_web")]
{
let handle = &self.0;
js! {
@(no_return)
var handle = @{handle};
window.removeEventListener("resize", handle);
}
}
}
}

View File

@ -1,22 +1,11 @@
//! This module contains Yew's implementation of a service to
//! use local and session storage of a browser.
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use std::fmt;
use thiserror::Error;
use web_sys::Storage;
use yew::format::Text;
cfg_if! {
if #[cfg(feature = "std_web")] {
#[allow(unused_imports)]
use stdweb::{_js_impl, js};
use stdweb::unstable::TryFrom;
use stdweb::web::{Storage};
} else if #[cfg(feature = "web_sys")] {
use yew::utils;
use web_sys::Storage;
}
}
use yew::utils;
/// Represents errors of a storage.
#[derive(Debug, Error)]
@ -48,30 +37,14 @@ impl fmt::Debug for StorageService {
impl StorageService {
/// Creates a new storage service instance with specified storage area.
pub fn new(area: Area) -> Result<Self, &'static str> {
let storage = cfg_match! {
feature = "std_web" => ({
let storage_name = match area {
Area::Local => "localStorage",
Area::Session => "sessionStorage",
};
let storage = js! {
try {
return window[@{storage_name}];
} catch(error) {
return error;
}
};
Storage::try_from(js!( return @{storage.as_ref()}; ))
}),
feature = "web_sys" => ({
let storage = {
match area {
Area::Local => utils::window().local_storage(),
Area::Session => utils::window().session_storage(),
}
};
storage.map(Option::unwrap)
}),
let storage = {
let storage = {
match area {
Area::Local => utils::window().local_storage(),
Area::Session => utils::window().session_storage(),
}
};
storage.map(Option::unwrap)
};
storage
@ -85,10 +58,7 @@ impl StorageService {
T: Into<Text>,
{
if let Ok(data) = value.into() {
let result = cfg_match! {
feature = "std_web" => self.storage.insert(key, &data),
feature = "web_sys" => self.storage.set_item(key, &data),
};
let result = self.storage.set_item(key, &data);
result.expect("can't insert value to a storage");
}
}
@ -98,19 +68,13 @@ impl StorageService {
where
T: From<Text>,
{
let data = cfg_match! {
feature = "std_web" => self.storage.get(key),
feature = "web_sys" => self.storage.get_item(key).unwrap(),
};
let data = self.storage.get_item(key).unwrap();
let data = data.ok_or_else(|| StorageError::CantRestore.into());
T::from(data)
}
/// Removes value from the storage.
pub fn remove(&mut self, key: &str) {
cfg_match! {
feature = "std_web" => self.storage.remove(key),
feature = "web_sys" => self.storage.remove_item(key).unwrap(),
};
self.storage.remove_item(key).unwrap();
}
}

View File

@ -2,28 +2,15 @@
//! send messages when a timeout has elapsed.
use super::Task;
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use gloo::timers::callback::Timeout;
use std::convert::TryInto;
use std::fmt;
use std::time::Duration;
use yew::callback::Callback;
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 gloo::timers::callback::Timeout;
}
}
/// A handle to cancel a timeout task.
#[must_use = "the timeout will be cleared when the task is dropped"]
pub struct TimeoutTask(
#[cfg(feature = "std_web")] Option<Value>,
#[cfg(feature = "web_sys")] Option<Timeout>,
);
pub struct TimeoutTask(Option<Timeout>);
impl fmt::Debug for TimeoutTask {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -49,21 +36,7 @@ impl TimeoutService {
.as_millis()
.try_into()
.expect("duration doesn't fit in u32");
let handle = cfg_match! {
feature = "std_web" => js! {
var callback = @{callback};
var action = function() {
callback();
callback.drop();
};
var delay = @{ms};
return {
timeout_id: setTimeout(action, delay),
callback: callback,
};
},
feature = "web_sys" => Timeout::new(ms, callback),
};
let handle = Timeout::new(ms, callback);
TimeoutTask(Some(handle))
}
}
@ -73,19 +46,3 @@ impl Task for TimeoutTask {
self.0.is_some()
}
}
impl Drop for TimeoutTask {
fn drop(&mut self) {
#[cfg(feature = "std_web")]
{
if self.is_active() {
let handle = &self.0;
js! { @(no_return)
var handle = @{handle};
clearTimeout(handle.timeout_id);
handle.callback.drop();
}
}
}
}
}

View File

@ -2,23 +2,13 @@
//! [`WebSocket` Protocol](https://tools.ietf.org/html/rfc6455).
use super::Task;
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use gloo::events::EventListener;
use js_sys::Uint8Array;
use std::fmt;
use wasm_bindgen::JsCast;
use web_sys::{BinaryType, Event, MessageEvent, WebSocket};
use yew::callback::Callback;
use yew::format::{Binary, FormatError, Text};
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::traits::IMessageEvent;
use stdweb::web::event::{SocketCloseEvent, SocketErrorEvent, SocketMessageEvent, SocketOpenEvent};
use stdweb::web::{IEventTarget, SocketBinaryType, SocketReadyState, WebSocket};
} else if #[cfg(feature = "web_sys")] {
use gloo::events::EventListener;
use js_sys::Uint8Array;
use wasm_bindgen::JsCast;
use web_sys::{BinaryType, Event, MessageEvent, WebSocket};
}
}
/// The status of a WebSocket connection. Used for status notifications.
#[derive(Clone, Debug, PartialEq)]
@ -44,12 +34,10 @@ pub enum WebSocketError {
pub struct WebSocketTask {
ws: WebSocket,
notification: Callback<WebSocketStatus>,
#[cfg(feature = "web_sys")]
#[allow(dead_code)]
listeners: [EventListener; 4],
}
#[cfg(feature = "web_sys")]
impl WebSocketTask {
fn new(
ws: WebSocket,
@ -87,23 +75,12 @@ impl WebSocketService {
where
OUT: From<Text> + From<Binary>,
{
cfg_match! {
feature = "std_web" => ({
let ws = Self::connect_common(url, &notification)?.0;
ws.add_event_listener(move |event: SocketMessageEvent| {
process_both(&event, &callback);
});
Ok(WebSocketTask { ws, notification })
}),
feature = "web_sys" => ({
let ConnectCommon(ws, listeners) = Self::connect_common(url, &notification)?;
let listener = EventListener::new(&ws, "message", move |event: &Event| {
let event = event.dyn_ref::<MessageEvent>().unwrap();
process_both(&event, &callback);
});
WebSocketTask::new(ws, notification, listener, listeners)
}),
}
let ConnectCommon(ws, listeners) = Self::connect_common(url, &notification)?;
let listener = EventListener::new(&ws, "message", move |event: &Event| {
let event = event.dyn_ref::<MessageEvent>().unwrap();
process_both(&event, &callback);
});
WebSocketTask::new(ws, notification, listener, listeners)
}
/// Connects to a server through a WebSocket connection, like connect,
@ -118,23 +95,12 @@ impl WebSocketService {
where
OUT: From<Binary>,
{
cfg_match! {
feature = "std_web" => ({
let ws = Self::connect_common(url, &notification)?.0;
ws.add_event_listener(move |event: SocketMessageEvent| {
process_binary(&event, &callback);
});
Ok(WebSocketTask { ws, notification })
}),
feature = "web_sys" => ({
let ConnectCommon(ws, listeners) = Self::connect_common(url, &notification)?;
let listener = EventListener::new(&ws, "message", move |event: &Event| {
let event = event.dyn_ref::<MessageEvent>().unwrap();
process_binary(&event, &callback);
});
WebSocketTask::new(ws, notification, listener, listeners)
}),
}
let ConnectCommon(ws, listeners) = Self::connect_common(url, &notification)?;
let listener = EventListener::new(&ws, "message", move |event: &Event| {
let event = event.dyn_ref::<MessageEvent>().unwrap();
process_binary(&event, &callback);
});
WebSocketTask::new(ws, notification, listener, listeners)
}
/// Connects to a server through a WebSocket connection, like connect,
@ -149,23 +115,12 @@ impl WebSocketService {
where
OUT: From<Text>,
{
cfg_match! {
feature = "std_web" => ({
let ws = Self::connect_common(url, &notification)?.0;
ws.add_event_listener(move |event: SocketMessageEvent| {
process_text(&event, &callback);
});
Ok(WebSocketTask { ws, notification })
}),
feature = "web_sys" => ({
let ConnectCommon(ws, listeners) = Self::connect_common(url, &notification)?;
let listener = EventListener::new(&ws, "message", move |event: &Event| {
let event = event.dyn_ref::<MessageEvent>().unwrap();
process_text(&event, &callback);
});
WebSocketTask::new(ws, notification, listener, listeners)
}),
}
let ConnectCommon(ws, listeners) = Self::connect_common(url, &notification)?;
let listener = EventListener::new(&ws, "message", move |event: &Event| {
let event = event.dyn_ref::<MessageEvent>().unwrap();
process_text(&event, &callback);
});
WebSocketTask::new(ws, notification, listener, listeners)
}
fn connect_common(
@ -174,79 +129,44 @@ impl WebSocketService {
) -> Result<ConnectCommon, WebSocketError> {
let ws = WebSocket::new(url);
let ws = ws.map_err(
#[cfg(feature = "std_web")]
|_| WebSocketError::CreationError("Error opening a WebSocket connection.".to_string()),
#[cfg(feature = "web_sys")]
|ws_error| {
WebSocketError::CreationError(
ws_error
.unchecked_into::<js_sys::Error>()
.to_string()
.as_string()
.unwrap(),
)
},
)?;
let ws = ws.map_err(|ws_error| {
WebSocketError::CreationError(
ws_error
.unchecked_into::<js_sys::Error>()
.to_string()
.as_string()
.unwrap(),
)
})?;
cfg_match! {
feature = "std_web" => ws.set_binary_type(SocketBinaryType::ArrayBuffer),
feature = "web_sys" => ws.set_binary_type(BinaryType::Arraybuffer),
ws.set_binary_type(BinaryType::Arraybuffer);
let notify = notification.clone();
let listener_open = move |_: &Event| {
notify.emit(WebSocketStatus::Opened);
};
let notify = notification.clone();
let listener_open =
move |#[cfg(feature = "std_web")] _: SocketOpenEvent,
#[cfg(feature = "web_sys")] _: &Event| {
notify.emit(WebSocketStatus::Opened);
};
let listener_close = move |_: &Event| {
notify.emit(WebSocketStatus::Closed);
};
let notify = notification.clone();
let listener_close =
move |#[cfg(feature = "std_web")] _: SocketCloseEvent,
#[cfg(feature = "web_sys")] _: &Event| {
notify.emit(WebSocketStatus::Closed);
};
let notify = notification.clone();
let listener_error =
move |#[cfg(feature = "std_web")] _: SocketErrorEvent,
#[cfg(feature = "web_sys")] _: &Event| {
notify.emit(WebSocketStatus::Error);
};
#[cfg_attr(feature = "std_web", allow(clippy::let_unit_value, unused_variables))]
{
let listeners = cfg_match! {
feature = "std_web" => ({
ws.add_event_listener(listener_open);
ws.add_event_listener(listener_close);
ws.add_event_listener(listener_error);
}),
feature = "web_sys" => [
EventListener::new(&ws, "open", listener_open),
EventListener::new(&ws, "close", listener_close),
EventListener::new(&ws, "error", listener_error),
],
};
Ok(ConnectCommon(
ws,
#[cfg(feature = "web_sys")]
listeners,
))
}
let listener_error = move |_: &Event| {
notify.emit(WebSocketStatus::Error);
};
let listeners = [
EventListener::new(&ws, "open", listener_open),
EventListener::new(&ws, "close", listener_close),
EventListener::new(&ws, "error", listener_error),
];
Ok(ConnectCommon(ws, listeners))
}
}
struct ConnectCommon(WebSocket, #[cfg(feature = "web_sys")] [EventListener; 3]);
struct ConnectCommon(WebSocket, [EventListener; 3]);
fn process_binary<OUT: 'static>(
#[cfg(feature = "std_web")] event: &SocketMessageEvent,
#[cfg(feature = "web_sys")] event: &MessageEvent,
callback: &Callback<OUT>,
) where
fn process_binary<OUT: 'static>(event: &MessageEvent, callback: &Callback<OUT>)
where
OUT: From<Binary>,
{
#[cfg(feature = "std_web")]
let bytes = event.data().into_array_buffer();
#[cfg(feature = "web_sys")]
let bytes = if !event.data().is_string() {
Some(event.data())
} else {
@ -254,10 +174,7 @@ fn process_binary<OUT: 'static>(
};
let data = if let Some(bytes) = bytes {
let bytes: Vec<u8> = cfg_match! {
feature = "std_web" => bytes.into(),
feature = "web_sys" => Uint8Array::new(&bytes).to_vec(),
};
let bytes: Vec<u8> = Uint8Array::new(&bytes).to_vec();
Ok(bytes)
} else {
Err(FormatError::ReceivedTextForBinary.into())
@ -267,17 +184,11 @@ fn process_binary<OUT: 'static>(
callback.emit(out);
}
fn process_text<OUT: 'static>(
#[cfg(feature = "std_web")] event: &SocketMessageEvent,
#[cfg(feature = "web_sys")] event: &MessageEvent,
callback: &Callback<OUT>,
) where
fn process_text<OUT: 'static>(event: &MessageEvent, callback: &Callback<OUT>)
where
OUT: From<Text>,
{
let text = cfg_match! {
feature = "std_web" => event.data().into_text(),
feature = "web_sys" => event.data().as_string(),
};
let text = event.data().as_string();
let data = if let Some(text) = text {
Ok(text)
@ -289,17 +200,10 @@ fn process_text<OUT: 'static>(
callback.emit(out);
}
fn process_both<OUT: 'static>(
#[cfg(feature = "std_web")] event: &SocketMessageEvent,
#[cfg(feature = "web_sys")] event: &MessageEvent,
callback: &Callback<OUT>,
) where
fn process_both<OUT: 'static>(event: &MessageEvent, callback: &Callback<OUT>)
where
OUT: From<Text> + From<Binary>,
{
#[cfg(feature = "std_web")]
let is_text = event.data().into_text().is_some();
#[cfg(feature = "web_sys")]
let is_text = event.data().is_string();
if is_text {
@ -316,10 +220,7 @@ impl WebSocketTask {
IN: Into<Text>,
{
if let Ok(body) = data.into() {
let result = cfg_match! {
feature = "std_web" => self.ws.send_text(&body),
feature = "web_sys" => self.ws.send_with_str(&body),
};
let result = self.ws.send_with_str(&body);
if result.is_err() {
self.notification.emit(WebSocketStatus::Error);
@ -333,10 +234,7 @@ impl WebSocketTask {
IN: Into<Binary>,
{
if let Ok(body) = data.into() {
let result = cfg_match! {
feature = "std_web" => self.ws.send_bytes(&body),
feature = "web_sys" => self.ws.send_with_u8_array(&body),
};
let result = self.ws.send_with_u8_array(&body);
if result.is_err() {
self.notification.emit(WebSocketStatus::Error);
@ -347,20 +245,14 @@ impl WebSocketTask {
impl Task for WebSocketTask {
fn is_active(&self) -> bool {
cfg_match! {
feature = "std_web" => self.ws.ready_state() == SocketReadyState::Open,
feature = "web_sys" => self.ws.ready_state() == WebSocket::OPEN,
}
self.ws.ready_state() == WebSocket::OPEN
}
}
impl Drop for WebSocketTask {
fn drop(&mut self) {
if self.is_active() {
cfg_match! {
feature = "std_web" => self.ws.close(),
feature = "web_sys" => self.ws.close().ok(),
};
self.ws.close().ok();
}
}
}
@ -428,7 +320,6 @@ mod tests {
}
#[test]
#[cfg(feature = "web_sys")]
async fn test_invalid_url_error() {
let url = "syntactically-invalid";
let cb_future = CallbackFuture::<Json<Result<Message, anyhow::Error>>>::default();

View File

@ -1,83 +0,0 @@
[package]
name = "yew-stdweb"
version = "0.17.4"
edition = "2018"
authors = [
"Denis Kolodin <deniskolodin@gmail.com>",
"Justin Starry <justin@yew.rs>",
]
repository = "https://github.com/yewstack/yew"
homepage = "https://github.com/yewstack/yew"
documentation = "https://docs.rs/yew-stdweb/"
license = "MIT/Apache-2.0"
readme = "../README.md"
keywords = ["web", "asmjs", "webasm", "javascript"]
categories = ["gui", "web-programming"]
description = "A framework for making client-side single-page apps"
[dependencies]
anyhow = "1"
anymap = "0.12"
bincode = { version = "1", optional = true }
cfg-if = "0.1"
cfg-match = "0.2"
console_error_panic_hook = { version = "0.1", optional = true }
fixedbitset = "0.3.0"
gloo = { version = "0.2.1", optional = true }
http = "0.2"
indexmap = "1.0.2"
js-sys = { version = "0.3", optional = true }
log = "0.4"
rmp-serde = { version = "0.14.0", optional = true }
serde = { version = "1.0", features = ["derive"] }
serde_cbor = { version = "0.11.1", optional = true }
serde_json = "1.0"
serde_yaml = { version = "0.8.3", optional = true }
slab = "0.4"
stdweb = { version = "0.4.20", optional = true }
thiserror = "1"
toml = { version = "0.5", optional = true }
wasm-bindgen = { version = "0.2.60", optional = true }
wasm-bindgen-futures = { version = "0.4", optional = true }
yew-macro = { version = "0.17.0", path = "../yew-macro", features = [
"std_web"
] }
# 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.60"
# 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.12.0"
ssri = "5.0.0"
[target.'cfg(target_os = "emscripten")'.dependencies]
ryu = "1.0.2" # 1.0.1 breaks emscripten
[dev-dependencies]
serde_derive = "1"
trybuild = "1.0"
rustversion = "1.0"
rmp-serde = "0.14.0"
bincode = "1"
[features]
default = ["agent", "std_web"]
std_web = ["stdweb"]
doc_test = []
wasm_test = []
agent = ["bincode"]
yaml = ["serde_yaml"]
msgpack = ["rmp-serde"]
cbor = ["serde_cbor"]
[package.metadata.docs.rs]
features = ["yaml", "cbor", "toml", "msgpack", "doc_test"]
[workspace]
members = [
"examples/counter",
"examples/todomvc",
]

View File

@ -1,19 +0,0 @@
# Yew Examples
### Running the examples
To start an example, enter its directory and start it with [cargo-web]:
```bash
cargo web start
```
To run an optimized build instead of a debug build use:
```bash
cargo web start --release
```
The `wasm32-unknown-unknown` target will be used by default, which is Rust's native WebAssembly target. The Emscripten-based `wasm32-unknown-emscripten` and `asmjs-unknown-emscripten` targets are also supported if you tell the `cargo-web` to build for them using the `--target` parameter.
[cargo-web]: https://github.com/koute/cargo-web

View File

@ -1,13 +0,0 @@
[package]
name = "counter"
version = "0.1.1"
authors = ["Denis Kolodin <deniskolodin@gmail.com>"]
edition = "2018"
[dependencies]
stdweb = "0.4.20"
yew = { path = "../..", package = "yew-stdweb" }
yew-services = { path = "../../../yew-services" }
[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies]
wasm-bindgen = "0.2.60"

View File

@ -1,62 +0,0 @@
#![recursion_limit = "256"]
use stdweb::web::Date;
use yew::{html, Component, ComponentLink, Html, ShouldRender};
use yew_services::ConsoleService;
pub struct Model {
link: ComponentLink<Self>,
value: i64,
}
pub enum Msg {
Increment,
Decrement,
}
impl Component for Model {
type Message = Msg;
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Model { link, value: 0 }
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::Increment => {
self.value += 1;
ConsoleService::log("plus one");
}
Msg::Decrement => {
self.value -= 1;
ConsoleService::log("minus one");
}
}
true
}
fn change(&mut self, _: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
html! {
<div>
<nav class="menu">
<button onclick=self.link.callback(|_| Msg::Increment)>
{ "Increment" }
</button>
<button onclick=self.link.callback(|_| Msg::Decrement)>
{ "Decrement" }
</button>
<button onclick=self.link.batch_callback(|_| vec![Msg::Increment, Msg::Increment])>
{ "Increment Twice" }
</button>
</nav>
<p>{ self.value }</p>
<p>{ Date::new().to_string() }</p>
</div>
}
}
}

View File

@ -1,3 +0,0 @@
fn main() {
yew::start_app::<counter::Model>();
}

View File

@ -1,16 +0,0 @@
[package]
name = "todomvc"
version = "0.1.0"
authors = ["Denis Kolodin <deniskolodin@gmail.com>"]
edition = "2018"
[dependencies]
strum = "0.13"
strum_macros = "0.13"
serde = "1"
serde_derive = "1"
yew = { path = "../..", package = "yew-stdweb" }
yew-services = { path = "../../../yew-services" }
[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies]
wasm-bindgen = "0.2.60"

View File

@ -1,6 +0,0 @@
## 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.

View File

@ -1,368 +0,0 @@
#![recursion_limit = "512"]
use serde_derive::{Deserialize, Serialize};
use std::borrow::Cow;
use strum::IntoEnumIterator;
use strum_macros::{EnumIter, ToString};
use yew::events::IKeyboardEvent;
use yew::format::Json;
use yew::{html, Component, ComponentLink, Html, InputData, KeyPressEvent, ShouldRender};
use yew_services::storage::{Area, StorageService};
const KEY: &str = "yew.todomvc.self";
pub struct Model {
link: ComponentLink<Self>,
storage: StorageService,
state: State,
}
#[derive(Serialize, Deserialize)]
pub struct State {
entries: Vec<Entry>,
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>) -> 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 change(&mut self, _: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
html! {
<div class="todomvc-wrapper">
<section class="todoapp">
<header class="header">
<h1>{ "todos" }</h1>
{ self.view_input() }
</header>
<section class="main">
<input
type="checkbox"
class="toggle-all"
checked=self.state.is_all_completed()
onclick=self.link.callback(|_| Msg::ToggleAll) />
<ul class="todo-list">
{ for self.state.entries.iter().filter(|e| self.state.filter.fit(e)).enumerate().map(|e| self.view_entry(e)) }
</ul>
</section>
<footer class="footer">
<span class="todo-count">
<strong>{ self.state.total() }</strong>
{ " item(s) left" }
</span>
<ul class="filters">
{ for Filter::iter().map(|flt| self.view_filter(flt)) }
</ul>
<button class="clear-completed" onclick=self.link.callback(|_| Msg::ClearCompleted)>
{ format!("Clear completed ({})", self.state.total_completed()) }
</button>
</footer>
</section>
<footer class="info">
<p>{ "Double-click to edit a todo" }</p>
<p>{ "Written by " }<a href="https://github.com/DenisKolodin/" target="_blank">{ "Denis Kolodin" }</a></p>
<p>{ "Part of " }<a href="http://todomvc.com/" target="_blank">{ "TodoMVC" }</a></p>
</footer>
</div>
}
}
}
impl Model {
fn view_filter(&self, filter: Filter) -> Html {
let cls = if self.state.filter == filter {
"selected"
} else {
"not-selected"
};
let flt = filter.clone();
html! {
<li>
<a class=cls
href=flt
onclick=self.link.callback(move |_| Msg::SetFilter(flt.clone()))>
{ filter }
</a>
</li>
}
}
fn view_input(&self) -> Html {
html! {
// You can use standard Rust comments. One line:
// <li></li>
<input class="new-todo"
placeholder="What needs to be done?"
value=&self.state.value
oninput=self.link.callback(|e: InputData| Msg::Update(e.value))
onkeypress=self.link.callback(|e: KeyPressEvent| {
if e.key() == "Enter" { Msg::Add } else { Msg::Nope }
}) />
/* Or multiline:
<ul>
<li></li>
</ul>
*/
}
}
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! {
<li class=class>
<div class="view">
<input
type="checkbox"
class="toggle"
checked=entry.completed
onclick=self.link.callback(move |_| Msg::Toggle(idx)) />
<label ondblclick=self.link.callback(move |_| Msg::ToggleEdit(idx))>{ &entry.description }</label>
<button class="destroy" onclick=self.link.callback(move |_| Msg::Remove(idx)) />
</div>
{ self.view_entry_edit_input((idx, &entry)) }
</li>
}
}
fn view_entry_edit_input(&self, (idx, entry): (usize, &Entry)) -> Html {
if entry.editing {
html! {
<input class="edit"
type="text"
value=&entry.description
oninput=self.link.callback(|e: InputData| Msg::UpdateEdit(e.value))
onblur=self.link.callback(move |_| Msg::Edit(idx))
onkeypress=self.link.callback(move |e: KeyPressEvent| {
if e.key() == "Enter" { Msg::Edit(idx) } else { Msg::Nope }
}) />
}
} else {
html! { <input type="hidden" /> }
}
}
}
#[derive(EnumIter, ToString, Clone, PartialEq, Serialize, Deserialize)]
pub enum Filter {
All,
Active,
Completed,
}
impl<'a> Into<Cow<'static, str>> for &'a Filter {
fn into(self) -> Cow<'static, str> {
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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
let &(idx, _) = entries.get(idx).unwrap();
idx
};
self.entries.remove(idx);
}
}

View File

@ -1,3 +0,0 @@
fn main() {
yew::start_app::<todomvc::Model>();
}

View File

@ -1,12 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Yew • TodoMVC</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/todomvc-common@1.0.5/base.css"/ >
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/todomvc-app-css@2.1.2/index.css" />
</head>
<body>
<script src="/todomvc.js"></script>
</body>
</html>

View File

@ -1 +0,0 @@
../yew/src

View File

@ -11,38 +11,36 @@ homepage = "https://github.com/yewstack/yew"
documentation = "https://docs.rs/yew/"
license = "MIT/Apache-2.0"
readme = "../README.md"
keywords = ["web", "asmjs", "webasm", "javascript"]
keywords = ["web", "webasm", "javascript"]
categories = ["gui", "wasm", "web-programming"]
description = "A framework for making client-side single-page apps"
[dependencies]
anyhow = "1"
anymap = "0.12"
bincode = { version = "1", optional = true }
cfg-if = "1.0"
cfg-match = "0.2"
console_error_panic_hook = { version = "0.1", optional = true }
gloo = { version = "0.2.1", optional = true }
console_error_panic_hook = "0.1"
gloo = "0.2.1"
http = "0.2"
indexmap = { version = "1.5", features = ["std"] }
js-sys = { version = "0.3", optional = true }
js-sys = "0.3"
log = "0.4"
rmp-serde = { version = "0.15.0", optional = true }
serde = { version = "1.0", features = ["derive"] }
serde_cbor = { version = "0.11.1", optional = true }
serde_json = "1.0"
serde_yaml = { version = "0.8.3", optional = true }
slab = "0.4"
stdweb = { version = "0.4.20", optional = true }
thiserror = "1"
toml = { version = "0.5", optional = true }
wasm-bindgen = { version = "0.2.60", optional = true }
wasm-bindgen-futures = { version = "0.4", optional = true }
wasm-bindgen = "0.2.60"
wasm-bindgen-futures = "0.4"
yew-macro = { version = "0.17.0", path = "../yew-macro" }
# optional encodings
bincode = { version = "1", optional = true }
rmp-serde = { version = "0.15.0", optional = true }
serde_cbor = { version = "0.11.1", optional = true }
serde_yaml = { version = "0.8.3", optional = true }
toml = { version = "0.5", optional = true }
[dependencies.web-sys]
version = "0.3"
optional = true
features = [
"AbortController",
"AbortSignal",
@ -100,38 +98,19 @@ features = [
"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.60"
# 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.13.0"
ssri = "6.0.0"
easybench-wasm = "0.2.1"
[target.'cfg(target_os = "emscripten")'.dependencies]
ryu = "1.0.2" # 1.0.1 breaks emscripten
[dev-dependencies]
serde_derive = "1"
trybuild = "1.0"
rustversion = "1.0"
rmp-serde = "0.15.0"
base64 = "0.13.0"
bincode = "1"
easybench-wasm = "0.2.1"
rmp-serde = "0.15.0"
rustversion = "1.0"
serde_derive = "1"
ssri = "6.0.0"
trybuild = "1.0"
wasm-bindgen-test = "0.3.4"
[features]
default = ["agent", "web_sys"]
std_web = ["stdweb", "yew-macro/std_web"]
web_sys = [
"console_error_panic_hook",
"gloo",
"js-sys",
"web-sys",
"wasm-bindgen",
"wasm-bindgen-futures"
]
default = ["agent"]
doc_test = []
wasm_test = []
wasm_bench = []

View File

@ -14,7 +14,7 @@ args = [
[tasks.doc-test]
clear = true
run_task = { name = ["doc-test-normal", "doc-test-stdweb"], fork = true }
run_task = { name = ["doc-test-normal"], fork = true }
[tasks.doc-test-normal]
command = "cargo"
@ -25,16 +25,6 @@ args = [
"doc_test,wasm_test,yaml,msgpack,cbor,toml",
]
[tasks.doc-test-stdweb]
command = "cargo"
args = [
"test",
"--doc",
"--features",
"doc_test,wasm_test,yaml,msgpack,cbor,toml,std_web,agent",
"--no-default-features",
]
[tasks.bench]
extend = "core::wasm-pack-base"
command = "wasm-pack"

View File

@ -1,16 +0,0 @@
use std::env;
pub fn main() {
let using_web_sys = cfg!(feature = "web_sys");
let using_std_web = cfg!(feature = "std_web");
if using_web_sys && using_std_web {
panic!("Yew does not allow the `web_sys` and `std_web` cargo features to be used simultaneously");
} else if !using_web_sys && !using_std_web {
panic!("Yew requires selecting either the `web_sys` or `std_web` cargo feature");
}
let using_cargo_web = env::var("COMPILING_UNDER_CARGO_WEB").is_ok();
if using_cargo_web && using_web_sys {
panic!("cargo-web is not compatible with web-sys");
}
}

View File

@ -6,21 +6,13 @@ pub use private::Private;
pub use public::Public;
use super::*;
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use crate::utils;
use js_sys::{Array, Reflect, Uint8Array};
use serde::{Deserialize, Serialize};
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};
}
}
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
use web_sys::{
Blob, BlobPropertyBag, DedicatedWorkerGlobalScope, MessageEvent, Url, Worker, WorkerOptions,
};
/// Implements rules to register a worker in a separate thread.
pub trait Threaded {
@ -69,27 +61,16 @@ enum FromWorker<T> {
ProcessOutput(HandlerId, T),
}
fn send_to_remote<AGN>(
#[cfg(feature = "std_web")] worker: &Value,
#[cfg(feature = "web_sys")] worker: &Worker,
msg: ToWorker<AGN::Input>,
) where
fn send_to_remote<AGN>(worker: &Worker, msg: ToWorker<AGN::Input>)
where
AGN: Agent,
<AGN as Agent>::Input: Serialize + for<'de> Deserialize<'de>,
<AGN as Agent>::Output: Serialize + for<'de> Deserialize<'de>,
{
let msg = msg.pack();
cfg_match! {
feature = "std_web" => js! {
var worker = @{worker};
var bytes = @{msg};
worker.postMessage(bytes);
},
feature = "web_sys" => worker.post_message_vec(msg),
};
worker.post_message_vec(msg);
}
#[cfg(feature = "web_sys")]
fn worker_new(name_of_resource: &str, is_module: bool) -> Worker {
let origin = utils::origin().unwrap();
let script_url = format!("{}/{}", origin, name_of_resource);
@ -123,19 +104,16 @@ fn worker_new(name_of_resource: &str, is_module: bool) -> 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<u8>));
fn post_message_vec(&self, data: Vec<u8>);
}
#[cfg(feature = "web_sys")]
macro_rules! worker_ext_impl {
($($type:ident),+) => {$(
impl WorkerExt for $type {
@ -157,7 +135,6 @@ macro_rules! worker_ext_impl {
)+};
}
#[cfg(feature = "web_sys")]
worker_ext_impl! {
Worker, DedicatedWorkerGlobalScope
}

View File

@ -1,20 +1,10 @@
use super::*;
use crate::callback::Callback;
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use queue::Queue;
use std::fmt;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicUsize, Ordering};
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 web_sys::{Worker};
}
}
use web_sys::Worker;
thread_local! {
static QUEUE: Queue<usize> = Queue::new();
@ -40,9 +30,7 @@ where
fn spawn_or_join(callback: Option<Callback<AGN::Output>>) -> Box<dyn Bridge<AGN>> {
let id = PRIVATE_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
let callback = callback.expect("Callback required for Private agents");
let handler = move |data: Vec<u8>,
#[cfg(feature = "std_web")] worker: Value,
#[cfg(feature = "web_sys")] worker: &Worker| {
let handler = move |data: Vec<u8>, worker: &Worker| {
let msg = FromWorker::<AGN::Output>::unpack(&data);
match msg {
FromWorker::WorkerLoaded => {
@ -51,13 +39,7 @@ where
if let Some(msgs) = queue.remove_msg_queue(&id) {
for msg in msgs {
cfg_match! {
feature = "std_web" => ({
let worker = &worker;
js! {@{worker}.postMessage(@{msg});};
}),
feature = "web_sys" => worker.post_message_vec(msg),
}
worker.post_message_vec(msg)
}
}
});
@ -71,21 +53,11 @@ where
// TODO(#947): Drop handler when bridge is dropped
let name_of_resource = AGN::name_of_resource();
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<u8>| handler(data, &worker_clone));
worker
}),
let worker = {
let worker = worker_new(name_of_resource, AGN::is_module());
let worker_clone = worker.clone();
worker.set_onmessage_closure(move |data: Vec<u8>| handler(data, &worker_clone));
worker
};
let bridge = PrivateBridge {
worker,
@ -104,9 +76,6 @@ where
<AGN as Agent>::Input: Serialize + for<'de> Deserialize<'de>,
<AGN as Agent>::Output: Serialize + for<'de> Deserialize<'de>,
{
#[cfg(feature = "std_web")]
worker: Value,
#[cfg(feature = "web_sys")]
worker: Worker,
_agent: PhantomData<AGN>,
id: usize,

View File

@ -1,9 +1,8 @@
use super::WorkerExt;
use super::*;
use crate::callback::Callback;
use crate::scheduler::Shared;
use anymap::{self, AnyMap};
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use queue::Queue;
use slab::Slab;
use std::any::TypeId;
@ -11,16 +10,7 @@ use std::cell::RefCell;
use std::fmt;
use std::marker::PhantomData;
use std::rc::Rc;
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 super::WorkerExt;
use web_sys::{Worker};
}
}
use web_sys::Worker;
thread_local! {
static REMOTE_AGENTS_POOL: RefCell<AnyMap> = RefCell::new(AnyMap::new());
@ -51,24 +41,18 @@ where
Rc::new(RefCell::new(Slab::new()));
let handler = {
let slab = slab.clone();
move |data: Vec<u8>,
#[cfg(feature = "std_web")] worker: Value,
#[cfg(feature = "web_sys")] worker: &Worker| {
move |data: Vec<u8>, worker: &Worker| {
let msg = FromWorker::<AGN::Output>::unpack(&data);
match msg {
FromWorker::WorkerLoaded => {
QUEUE.with(|queue| {
queue.insert_loaded_agent(TypeId::of::<AGN>());
if let Some(msgs) = queue.remove_msg_queue(&TypeId::of::<AGN>()) {
if let Some(msgs) =
queue.remove_msg_queue(&TypeId::of::<AGN>())
{
for msg in msgs {
cfg_match! {
feature = "std_web" => ({
let worker = &worker;
js! {@{worker}.postMessage(@{msg});};
}),
feature = "web_sys" => worker.post_message_vec(msg),
}
worker.post_message_vec(msg)
}
}
});
@ -80,23 +64,13 @@ where
}
};
let name_of_resource = AGN::name_of_resource();
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<u8>| {
handler(data, &worker_clone);
});
worker
}),
let worker = {
let worker = worker_new(name_of_resource, AGN::is_module());
let worker_clone = worker.clone();
worker.set_onmessage_closure(move |data: Vec<u8>| {
handler(data, &worker_clone);
});
worker
};
let launched = RemoteAgent::new(worker, slab);
entry.insert(launched).create_bridge(callback)
@ -122,9 +96,6 @@ where
<AGN as Agent>::Input: Serialize + for<'de> Deserialize<'de>,
<AGN as Agent>::Output: Serialize + for<'de> Deserialize<'de>,
{
#[cfg(feature = "std_web")]
worker: Value,
#[cfg(feature = "web_sys")]
worker: Worker,
id: HandlerId,
_agent: PhantomData<AGN>,
@ -220,13 +191,7 @@ where
fn respond(&self, id: HandlerId, output: AGN::Output) {
let msg = FromWorker::ProcessOutput(id, output);
let data = msg.pack();
cfg_match! {
feature = "std_web" => js! {
var data = @{data};
self.postMessage(data);
},
feature = "web_sys" => worker_self().post_message_vec(data),
};
worker_self().post_message_vec(data);
}
}
@ -261,29 +226,15 @@ where
let upd = AgentLifecycleEvent::Destroy;
scope.send(upd);
// Terminates web worker
cfg_match! {
feature = "std_web" => js! { self.close(); },
feature = "web_sys" => worker_self().close(),
};
worker_self().close();
}
}
};
let loaded: FromWorker<AGN::Output> = FromWorker::WorkerLoaded;
let loaded = loaded.pack();
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);
}),
};
let worker = worker_self();
worker.set_onmessage_closure(handler);
worker.post_message_vec(loaded);
}
}
@ -293,9 +244,6 @@ where
<AGN as Agent>::Input: Serialize + for<'de> Deserialize<'de>,
<AGN as Agent>::Output: Serialize + for<'de> Deserialize<'de>,
{
#[cfg(feature = "std_web")]
worker: Value,
#[cfg(feature = "web_sys")]
worker: Worker,
slab: SharedOutputSlab<AGN>,
}
@ -306,11 +254,7 @@ where
<AGN as Agent>::Input: Serialize + for<'de> Deserialize<'de>,
<AGN as Agent>::Output: Serialize + for<'de> Deserialize<'de>,
{
pub fn new(
#[cfg(feature = "std_web")] worker: Value,
#[cfg(feature = "web_sys")] worker: Worker,
slab: SharedOutputSlab<AGN>,
) -> Self {
pub fn new(worker: Worker, slab: SharedOutputSlab<AGN>) -> Self {
RemoteAgent { worker, slab }
}

View File

@ -3,14 +3,7 @@
use crate::html::{Component, ComponentLink, NodeRef, Scope};
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;
}
}
use web_sys::Element;
/// An instance of an application.
#[derive(Debug)]
@ -89,6 +82,7 @@ where
{
/// Creates a new `App` with a component in a context.
pub fn new() -> Self {
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
let scope = Scope::new(None);
App { scope }
}

View File

@ -4,14 +4,7 @@ use super::{Component, Scope};
use crate::scheduler::{scheduler, ComponentRunnableType, Runnable, Shared};
use crate::virtual_dom::{VDiff, VNode};
use crate::NodeRef;
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::web::Element;
} else if #[cfg(feature = "web_sys")] {
use web_sys::Element;
}
}
use web_sys::Element;
pub(crate) struct ComponentState<COMP: Component> {
pub(crate) component: Box<COMP>,

View File

@ -11,19 +11,12 @@ use crate::html::NodeRef;
use crate::scheduler::{scheduler, Shared};
use crate::utils::document;
use crate::virtual_dom::{insert_node, VNode};
use cfg_if::cfg_if;
use std::any::{Any, TypeId};
use std::cell::{Ref, RefCell};
use std::fmt;
use std::ops::Deref;
use std::rc::Rc;
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::web::{Element, Node};
} else if #[cfg(feature = "web_sys")] {
use web_sys::{Element, Node};
}
}
use web_sys::{Element, Node};
/// Untyped scope used for accessing parent scope
#[derive(Debug, Clone)]

View File

@ -1,101 +0,0 @@
// Inspired by: http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Events
// Yew maintains a list of event listeners not supported by stdweb in the yew-macro crate (in the tag_attribute.rs file).
// Be sure to update that list when you change anything here.
impl_action! {
onabort(event: ResourceAbortEvent) -> ResourceAbortEvent => |_, event| { event }
onauxclick(event: AuxClickEvent) -> AuxClickEvent => |_, event| { event }
onblur(event: BlurEvent) -> BlurEvent => |_, event| { event }
// oncancel not supported
// oncanplay not supported
// oncanplaythrough not supported
onchange(event: ChangeEvent) -> ChangeData => |this: &Element, _| { onchange_handler(this) }
onclick(event: ClickEvent) -> ClickEvent => |_, event| { event }
// onclose not supported
oncontextmenu(event: ContextMenuEvent) -> ContextMenuEvent => |_, event| { event }
// oncuechange not supported
ondblclick(event: DoubleClickEvent) -> DoubleClickEvent => |_, event| { event }
ondrag(event: DragEvent) -> DragEvent => |_, event| { event }
ondragend(event: DragEndEvent) -> DragEndEvent => |_, event| { event }
ondragenter(event: DragEnterEvent) -> DragEnterEvent => |_, event| { event }
ondragexit(event: DragExitEvent) -> DragExitEvent => |_, event| { event }
ondragleave(event: DragLeaveEvent) -> DragLeaveEvent => |_, event| { event }
ondragover(event: DragOverEvent) -> DragOverEvent => |_, event| { event }
ondragstart(event: DragStartEvent) -> DragStartEvent => |_, event| { event }
ondrop(event: DragDropEvent) -> DragDropEvent => |_, event| { event }
// ondurationchange not supported
// onemptied not supported
// onended not supported
onerror(event: ResourceErrorEvent) -> ResourceErrorEvent => |_, event| { event }
onfocus(event: FocusEvent) -> FocusEvent => |_, event| { event }
// onformdata not supported
oninput(event: InputEvent) -> InputData => |this: &Element, event| { oninput_handler(this, event) }
// oninvalid not supported
onkeydown(event: KeyDownEvent) -> KeyDownEvent => |_, event| { event }
onkeypress(event: KeyPressEvent) -> KeyPressEvent => |_, event| { event }
onkeyup(event: KeyUpEvent) -> KeyUpEvent => |_, event| { event }
onload(event: ResourceLoadEvent) -> ResourceLoadEvent => |_, event| { event }
// onloadeddata not supported
// onloadedmetadata not supported
onloadstart(event: LoadStartEvent) -> LoadStartEvent => |_, event| { event }
onmousedown(event: MouseDownEvent) -> MouseDownEvent => |_, event| { event }
onmouseenter(event: MouseEnterEvent) -> MouseEnterEvent => |_, event| { event }
onmouseleave(event: MouseLeaveEvent) -> MouseLeaveEvent => |_, event| { event }
onmousemove(event: MouseMoveEvent) -> MouseMoveEvent => |_, event| { event }
onmouseout(event: MouseOutEvent) -> MouseOutEvent => |_, event| { event }
onmouseover(event: MouseOverEvent) -> MouseOverEvent => |_, event| { event }
onmouseup(event: MouseUpEvent) -> MouseUpEvent => |_, event| { event }
// onpause not supported
// onplay not supported
// onplaying not supported
onprogress(event: ProgressEvent) -> ProgressEvent => |_, event| { event }
// onratechange not supported
// onreset not supported
onresize(event: ResizeEvent) -> ResizeEvent => |_, event| { event }
onscroll(event: ScrollEvent) -> ScrollEvent => |_, event| { event }
// onsecuritypolicyviolation not supported
// onseeked not supported
// onseeking not supported
// onselect not supported
onslotchange(event: SlotChangeEvent) -> SlotChangeEvent => |_, event| { event }
// onstalled not supported
onsubmit(event: SubmitEvent) -> SubmitEvent => |_, event| { event }
// onsuspend not supported
// ontimeupdate not supported
// ontoggle not supported
// onvolumechange not supported
// onwaiting not supported
onwheel(event: MouseWheelEvent) -> MouseWheelEvent => |_, event| { event }
// oncopy not supported
// oncut not supported
// onpaste not supported
// onanimationcancel not supported
// onanimationend not supported
// onanimationiteration not supported
// onanimationstart not supported
ongotpointercapture(event: GotPointerCaptureEvent) -> GotPointerCaptureEvent => |_, event| { event }
onloadend(event: LoadEndEvent) -> LoadEndEvent => |_, 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 }
onpointerlockchange(event: PointerLockChangeEvent) -> PointerLockChangeEvent => |_, event| { event }
onpointerlockerror(event: PointerLockErrorEvent) -> PointerLockErrorEvent => |_, 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 }
onselectionchange(event: SelectionChangeEvent) -> SelectionChangeEvent => |_, event| { event }
// onselectstart not supported
// onshow not supported
ontouchcancel(event: TouchCancel) -> TouchCancel => |_, event| { event }
ontouchend(event: TouchEnd) -> TouchEnd => |_, event| { event }
ontouchmove(event: TouchMove) -> TouchMove => |_, event| { event }
ontouchstart(event: TouchStart) -> TouchStart => |_, event| { event }
// ontransitioncancel not supported
// ontransitionend not supported
// ontransitionrun not supported
// ontransitionstart not supported
}

View File

@ -1,28 +1,16 @@
#[macro_use]
macro_rules! impl_action {
($($action:ident(event: $type:ident) -> $ret:ty => $convert:expr)*) => {$(
impl_action!($action(name: "", event: $type) -> $ret => $convert);
)*};
($($action:ident(name: $name:literal, event: $type:ident) -> $ret:ty => $convert:expr)*) => {$(
/// An abstract implementation of a listener.
#[doc(hidden)]
pub mod $action {
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use crate::callback::Callback;
#[allow(unused_imports)]
use crate::html::listener::*;
use crate::virtual_dom::Listener;
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::web::event::$type;
use stdweb::web::{Element, IEventTarget};
} else if #[cfg(feature = "web_sys")] {
use gloo::events::{EventListener, EventListenerOptions};
use wasm_bindgen::JsValue;
use web_sys::{$type as WebSysType, Element, EventTarget};
}
}
use gloo::events::{EventListener, EventListenerOptions};
use wasm_bindgen::JsValue;
use web_sys::{$type as WebSysType, Element, EventTarget};
/// A wrapper for a callback which attaches event listeners to elements.
#[derive(Clone, Debug)]
@ -49,25 +37,18 @@ macro_rules! impl_action {
let this = element.clone();
let callback = self.callback.clone();
let listener = move |
#[cfg(feature = "std_web")] event: $type,
#[cfg(feature = "web_sys")] event: &web_sys::Event
event: &web_sys::Event
| {
#[cfg(feature = "web_sys")]
let event: WebSysType = JsValue::from(event).into();
callback.emit($convert(&this, event));
};
cfg_match! {
feature = "std_web" => EventListener(Some(element.add_event_listener(listener))),
feature = "web_sys" => ({
// We should only set passive event listeners for `touchstart` and `touchmove`.
// See here: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners
if $name == "touchstart" || $name == "touchmove" {
EventListener::new(&EventTarget::from(element.clone()), $name, listener)
} else {
let options = EventListenerOptions::enable_prevent_default();
EventListener::new_with_options(&EventTarget::from(element.clone()), $name, options, listener)
}
}),
// We should only set passive event listeners for `touchstart` and `touchmove`.
// See here: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners
if $name == "touchstart" || $name == "touchmove" {
EventListener::new(&EventTarget::from(element.clone()), $name, listener)
} else {
let options = EventListenerOptions::enable_prevent_default();
EventListener::new_with_options(&EventTarget::from(element.clone()), $name, options, listener)
}
}
}

View File

@ -1,33 +1,14 @@
#[macro_use]
mod macros;
mod events;
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use wasm_bindgen::JsCast;
use web_sys::{
Element, FileList, HtmlInputElement as InputElement, HtmlSelectElement as SelectElement,
HtmlTextAreaElement as TextAreaElement, InputEvent,
};
cfg_if! {
if #[cfg(feature = "std_web")] {
mod listener_stdweb;
use stdweb::js;
use stdweb::unstable::{TryFrom, TryInto};
use stdweb::web::html_element::{InputElement, SelectElement, TextAreaElement};
use stdweb::web::{Element, EventListenerHandle, FileList, IElement, INode};
use stdweb::web::event::InputEvent;
pub use listener_stdweb::*;
} else if #[cfg(feature = "web_sys")] {
mod listener_web_sys;
use wasm_bindgen::JsCast;
use web_sys::{
Element, FileList, HtmlInputElement as InputElement, HtmlSelectElement as SelectElement,
HtmlTextAreaElement as TextAreaElement,
InputEvent
};
pub use listener_web_sys::*;
}
}
pub use events::*;
/// A type representing data from `oninput` event.
#[derive(Debug)]
@ -64,26 +45,10 @@ fn oninput_handler(this: &Element, event: InputEvent) -> InputData {
// 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, v2) = cfg_match! {
feature = "std_web" => ({
(
this.clone()
.try_into()
.map(|input: InputElement| input.raw_value())
.ok(),
this.clone()
.try_into()
.map(|input: TextAreaElement| input.value())
.ok(),
)
}),
feature = "web_sys" => ({
(
this.dyn_ref().map(|input: &InputElement| input.value()),
this.dyn_ref().map(|input: &TextAreaElement| input.value()),
)
}),
};
let (v1, v2) = (
this.dyn_ref().map(|input: &InputElement| input.value()),
this.dyn_ref().map(|input: &TextAreaElement| input.value()),
);
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");
@ -93,39 +58,24 @@ fn oninput_handler(this: &Element, event: InputEvent) -> InputData {
fn onchange_handler(this: &Element) -> ChangeData {
match this.node_name().as_ref() {
"INPUT" => {
let input = cfg_match! {
feature = "std_web" => InputElement::try_from(this.clone()).unwrap(),
feature = "web_sys" => this.dyn_ref::<InputElement>().unwrap(),
};
let input = this.dyn_ref::<InputElement>().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 = cfg_match! {
feature = "std_web" => js!( return @{input}.files; ).try_into().unwrap(),
feature = "web_sys" => input.files().unwrap(),
};
let files: FileList = input.files().unwrap();
ChangeData::Files(files)
} else {
cfg_match! {
feature = "std_web" => ChangeData::Value(input.raw_value()),
feature = "web_sys" => ChangeData::Value(input.value()),
}
ChangeData::Value(input.value())
}
}
"TEXTAREA" => {
let tae = cfg_match! {
feature = "std_web" => TextAreaElement::try_from(this.clone()).unwrap(),
feature = "web_sys" => this.dyn_ref::<TextAreaElement>().unwrap(),
};
let tae = this.dyn_ref::<TextAreaElement>().unwrap();
ChangeData::Value(tae.value())
}
"SELECT" => {
let se = cfg_match! {
feature = "std_web" => SelectElement::try_from(this.clone()).unwrap(),
feature = "web_sys" => this.dyn_ref::<SelectElement>().unwrap().clone(),
};
let se = this.dyn_ref::<SelectElement>().unwrap().clone();
ChangeData::Select(se)
}
_ => {
@ -133,17 +83,3 @@ fn onchange_handler(this: &Element) -> ChangeData {
}
}
}
/// Handler to an event listener, only use is to cancel the event.
#[cfg(feature = "std_web")]
#[derive(Debug)]
pub struct EventListener(Option<EventListenerHandle>);
#[cfg(feature = "std_web")]
impl Drop for EventListener {
fn drop(&mut self) {
if let Some(event) = self.0.take() {
event.remove()
}
}
}

View File

@ -9,19 +9,10 @@ pub use component::*;
pub use listener::*;
use crate::virtual_dom::VNode;
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use std::cell::RefCell;
use std::rc::Rc;
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::unstable::TryFrom;
use stdweb::web::Node;
} else if #[cfg(feature = "web_sys")] {
use wasm_bindgen::JsValue;
use web_sys::Node;
}
}
use wasm_bindgen::JsValue;
use web_sys::Node;
/// A type which expected as a result of `view` function implementation.
pub type Html = VNode;
@ -31,10 +22,7 @@ pub type Html = VNode;
/// # Example
/// Focus an `<input>` element on mount.
/// ```
/// #[cfg(feature = "std_web")]
/// use stdweb::web::{html_element::InputElement, IHtmlElement};
/// #[cfg(feature = "web_sys")]
/// use web_sys::HtmlInputElement as InputElement;
/// use web_sys::HtmlInputElement;
///# use yew::prelude::*;
///
/// pub struct Input {
@ -53,7 +41,7 @@ pub type Html = VNode;
///
/// fn rendered(&mut self, first_render: bool) {
/// if first_render {
/// if let Some(input) = self.node_ref.cast::<InputElement>() {
/// if let Some(input) = self.node_ref.cast::<HtmlInputElement>() {
/// input.focus();
/// }
/// }
@ -99,17 +87,9 @@ impl NodeRef {
}
/// Try converting the node reference into another form
pub fn cast<
#[cfg(feature = "std_web")] INTO: TryFrom<Node>,
#[cfg(feature = "web_sys")] INTO: AsRef<Node> + From<JsValue>,
>(
&self,
) -> Option<INTO> {
pub fn cast<INTO: AsRef<Node> + From<JsValue>>(&self) -> Option<INTO> {
let node = self.get();
cfg_match! {
feature = "std_web" => node.and_then(|node| INTO::try_from(node).ok()),
feature = "web_sys" => node.map(Into::into).map(INTO::from),
}
node.map(Into::into).map(INTO::from)
}
/// Wrap an existing `Node` in a `NodeRef`

View File

@ -11,12 +11,6 @@
//!
//! ### Supported Targets
//! - `wasm32-unknown-unknown`
#![cfg_attr(
feature = "std_web",
doc = "\
- `wasm32-unknown-emscripten`
- `asmjs-unknown-emscripten`"
)]
//!
//! ### Important Notes
//! - Yew is not (yet) production ready but is great for side projects and internal tools
@ -24,12 +18,6 @@
feature = "web_sys",
doc = " - If your app is built with `stdweb`, we recommend using [`yew-stdweb`](https://docs.rs/yew-stdweb) instead."
)]
#![cfg_attr(
feature = "std_web",
doc = "\
- We recommend aliasing `yew-stdweb` to `yew` in your Cargo.toml: `yew = { package = \"yew-stdweb\", .. }`
- If your app is built with `web-sys`, we recommend using [`yew`](https://docs.rs/yew) instead."
)]
//!
//! ## Example
//!
@ -291,63 +279,26 @@ pub mod virtual_dom;
#[cfg(feature = "agent")]
pub mod agent;
#[cfg(feature = "web_sys")]
pub use web_sys;
/// The module that contains all events available in the framework.
pub mod events {
use cfg_if::cfg_if;
pub use crate::html::{ChangeData, InputData};
cfg_if! {
if #[cfg(feature = "std_web")] {
#[doc(no_inline)]
pub use stdweb::web::event::{
BlurEvent, ClickEvent, ContextMenuEvent, DoubleClickEvent, DragDropEvent, DragEndEvent,
DragEnterEvent, DragEvent, DragExitEvent, DragLeaveEvent, DragOverEvent, DragStartEvent,
FocusEvent, GotPointerCaptureEvent, IKeyboardEvent, IMouseEvent, IPointerEvent,
KeyDownEvent, KeyPressEvent, KeyUpEvent, LostPointerCaptureEvent, MouseDownEvent,
MouseEnterEvent, MouseLeaveEvent, MouseMoveEvent, MouseOutEvent, MouseOverEvent,
MouseUpEvent, MouseWheelEvent, PointerCancelEvent, PointerDownEvent, PointerEnterEvent,
PointerLeaveEvent, PointerMoveEvent, PointerOutEvent, PointerOverEvent, PointerUpEvent,
ScrollEvent, SubmitEvent, TouchCancel, TouchEnd, TouchEnter, TouchMove, TouchStart,
};
} else if #[cfg(feature = "web_sys")] {
#[doc(no_inline)]
pub use web_sys::{
AnimationEvent, DragEvent, ErrorEvent, Event, FocusEvent, InputEvent, KeyboardEvent,
MouseEvent, PointerEvent, ProgressEvent, TouchEvent, TransitionEvent, UiEvent, WheelEvent,
};
}
}
}
use cfg_match::cfg_match;
/// Initializes yew framework. It should be called first.
pub fn initialize() {
cfg_match! {
feature = "std_web" => stdweb::initialize(),
feature = "web_sys" => std::panic::set_hook(Box::new(console_error_panic_hook::hook)),
#[doc(no_inline)]
pub use web_sys::{
AnimationEvent, DragEvent, ErrorEvent, Event, FocusEvent, InputEvent, KeyboardEvent,
MouseEvent, PointerEvent, ProgressEvent, TouchEvent, TransitionEvent, UiEvent, WheelEvent,
};
}
/// Starts event loop.
pub fn run_loop() {
#[cfg(feature = "std_web")]
stdweb::event_loop();
}
/// Starts an app mounted to a body of the document.
pub fn start_app<COMP>()
where
COMP: Component,
COMP::Properties: Default,
{
initialize();
App::<COMP>::new().mount_to_body();
run_loop();
}
/// Starts an app mounted to a body of the document.
@ -355,9 +306,7 @@ pub fn start_app_with_props<COMP>(props: COMP::Properties)
where
COMP: Component,
{
initialize();
App::<COMP>::new().mount_to_body_with_props(props);
run_loop();
}
/// The Yew Prelude

View File

@ -1,32 +1,18 @@
//! This module contains useful utilities to get information about the current document.
use anyhow::{anyhow, Error};
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use std::marker::PhantomData;
use web_sys::{Document, Window};
use yew::html::ChildrenRenderer;
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::web::{Document, Window};
} else if #[cfg(feature = "web_sys")] {
use web_sys::{Document, Window};
}
}
/// Returns the current window. This function will panic if there is no available window.
pub fn window() -> Window {
cfg_match! {
feature = "std_web" => stdweb::web::window(),
feature = "web_sys" => web_sys::window().expect("no window available"),
}
web_sys::window().expect("no window available")
}
/// Returns the current document.
pub fn document() -> Document {
cfg_match! {
feature = "std_web" => stdweb::web::document(),
feature = "web_sys" => window().document().unwrap(),
}
window().document().unwrap()
}
/// Returns the `host` for the current document. Useful for connecting to the server which serves
@ -36,10 +22,6 @@ pub fn host() -> Result<String, Error> {
.location()
.ok_or_else(|| anyhow!("can't get location"))?;
#[cfg(feature = "std_web")]
let host = location.host().map_err(Error::from)?;
#[cfg(feature = "web_sys")]
let host = location.host().map_err(|e| {
anyhow!(e
.as_string()
@ -53,13 +35,6 @@ pub fn host() -> Result<String, Error> {
pub fn origin() -> Result<String, Error> {
let location = window().location();
#[cfg(feature = "std_web")]
let location = location.ok_or_else(|| anyhow!("can't get location"))?;
#[cfg(feature = "std_web")]
let origin = location.origin().map_err(Error::from)?;
#[cfg(feature = "web_sys")]
let origin = location.origin().map_err(|e| {
anyhow!(e
.as_string()

View File

@ -14,18 +14,10 @@ pub mod vtag;
pub mod vtext;
use crate::html::{AnyScope, NodeRef};
use cfg_if::cfg_if;
use gloo::events::EventListener;
use indexmap::IndexMap;
use std::{borrow::Cow, collections::HashMap, fmt, hint::unreachable_unchecked, iter, mem, rc::Rc};
cfg_if! {
if #[cfg(feature = "std_web")] {
use crate::html::EventListener;
use stdweb::web::{Element, INode, Node};
} else if #[cfg(feature = "web_sys")] {
use gloo::events::EventListener;
use web_sys::{Element, Node};
}
}
use web_sys::{Element, Node};
#[doc(inline)]
pub use self::key::Key;
@ -361,7 +353,6 @@ pub(crate) trait VDiff {
) -> NodeRef;
}
#[cfg(feature = "web_sys")]
pub(crate) fn insert_node(node: &Node, parent: &Element, next_sibling: Option<Node>) {
match next_sibling {
Some(next_sibling) => parent
@ -371,25 +362,13 @@ pub(crate) fn insert_node(node: &Node, parent: &Element, next_sibling: Option<No
};
}
#[cfg(feature = "std_web")]
pub(crate) fn insert_node(node: &impl INode, parent: &impl INode, next_sibling: Option<Node>) {
if let Some(next_sibling) = next_sibling {
parent
.insert_before(node, &next_sibling)
.expect("failed to insert tag before next sibling");
} else {
parent.append_child(node);
}
}
/// Transform properties to the expected type.
pub trait Transformer<FROM, TO> {
/// Transforms one type to another.
fn transform(from: FROM) -> TO;
}
// stdweb lacks the `inner_html` method
#[cfg(all(test, feature = "web_sys"))]
#[cfg(test)]
mod layout_tests {
use super::*;
use crate::html::{AnyScope, Scope};
@ -530,7 +509,7 @@ mod layout_tests {
}
}
#[cfg(all(test, feature = "web_sys", feature = "wasm_bench"))]
#[cfg(all(test, feature = "wasm_bench"))]
mod benchmarks {
use super::{Attributes, PositionalAttr};
use std::borrow::Cow;

View File

@ -2,18 +2,11 @@
use super::{Key, Transformer, VDiff, VNode};
use crate::html::{AnyScope, Component, NodeRef, Scope, Scoped};
use cfg_if::cfg_if;
use std::any::TypeId;
use std::borrow::Borrow;
use std::fmt;
use std::ops::Deref;
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::web::Element;
} else if #[cfg(feature = "web_sys")] {
use web_sys::Element;
}
}
use web_sys::Element;
/// A virtual component.
pub struct VComp {
@ -274,15 +267,7 @@ mod tests {
html, utils::document, Children, Component, ComponentLink, Html, NodeRef, Properties,
ShouldRender,
};
use cfg_match::cfg_match;
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::web::{INode, Node};
} else if #[cfg(feature = "web_sys")] {
use web_sys::Node;
}
}
use web_sys::Node;
#[cfg(feature = "wasm_test")]
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
@ -471,10 +456,8 @@ mod tests {
}
}
#[cfg(feature = "web_sys")]
use super::{AnyScope, Element};
#[cfg(feature = "web_sys")]
fn setup_parent() -> (AnyScope, Element) {
let scope = AnyScope {
type_id: std::any::TypeId::of::<()>(),
@ -488,7 +471,6 @@ mod tests {
(scope, parent)
}
#[cfg(feature = "web_sys")]
fn get_html(mut node: Html, scope: &AnyScope, parent: &Element) -> String {
// clear parent
parent.set_inner_html("");
@ -498,7 +480,6 @@ mod tests {
}
#[test]
#[cfg(feature = "web_sys")]
fn all_ways_of_passing_children_work() {
let (scope, parent) = setup_parent();
@ -553,25 +534,19 @@ mod tests {
};
let parent = document().create_element("div").unwrap();
#[cfg(feature = "std_web")]
document().body().unwrap().append_child(&parent);
#[cfg(feature = "web_sys")]
document().body().unwrap().append_child(&parent).unwrap();
let node_ref = NodeRef::default();
let mut elem: VNode = html! { <Comp ref=node_ref.clone()></Comp> };
elem.apply(&scope, &parent, NodeRef::default(), None);
let parent_node = cfg_match! {
feature = "std_web" => parent.as_node(),
feature = "web_sys" => parent.deref(),
};
let parent_node = parent.deref();
assert_eq!(node_ref.get(), parent_node.first_child());
elem.detach(&parent);
assert!(node_ref.get().is_none());
}
}
#[cfg(all(test, feature = "web_sys"))]
#[cfg(test)]
mod layout_tests {
extern crate self as yew;

View File

@ -1,16 +1,9 @@
//! This module contains fragments implementation.
use super::{Key, VDiff, VNode, VText};
use crate::html::{AnyScope, NodeRef};
use cfg_if::cfg_if;
use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut};
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::web::Element;
} else if #[cfg(feature = "web_sys")] {
use web_sys::Element;
}
}
use web_sys::Element;
/// This struct represents a fragment of the Virtual DOM tree.
#[derive(Clone, Debug, PartialEq, Default)]
@ -235,7 +228,7 @@ impl VDiff for VList {
}
}
#[cfg(all(test, feature = "web_sys"))]
#[cfg(test)]
mod layout_tests {
extern crate self as yew;
@ -313,7 +306,7 @@ mod layout_tests {
}
}
#[cfg(all(test, feature = "web_sys"))]
#[cfg(test)]
mod layout_tests_keys {
extern crate self as yew;

View File

@ -2,20 +2,12 @@
use super::{Key, VChild, VComp, VDiff, VList, VTag, VText};
use crate::html::{AnyScope, Component, NodeRef};
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use log::warn;
use std::cmp::PartialEq;
use std::fmt;
use std::iter::FromIterator;
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::web::{Element, INode, Node};
} else if #[cfg(feature = "web_sys")] {
use web_sys::{Element, Node};
}
}
use web_sys::{Element, Node};
/// Bind virtual element to a DOM reference.
#[derive(Clone)]
@ -54,10 +46,7 @@ impl VNode {
.into(),
VNode::VText(vtext) => {
let text_node = vtext.reference.as_ref().expect("VText is not mounted");
cfg_match! {
feature = "std_web" => text_node.as_node().clone(),
feature = "web_sys" => text_node.clone().into(),
}
text_node.clone().into()
}
VNode::VComp(vcomp) => vcomp.node_ref.get().expect("VComp is not mounted"),
VNode::VList(vlist) => vlist
@ -218,7 +207,7 @@ impl PartialEq for VNode {
}
}
#[cfg(all(test, feature = "web_sys"))]
#[cfg(test)]
mod layout_tests {
use super::*;
use crate::virtual_dom::layout_tests::{diff_layouts, TestLayout};

View File

@ -5,29 +5,17 @@ use super::{
};
use crate::html::{AnyScope, NodeRef};
use crate::utils::document;
use cfg_if::cfg_if;
use cfg_match::cfg_match;
use gloo::events::EventListener;
use log::warn;
use std::borrow::Cow;
use std::cmp::PartialEq;
use std::ops::Deref;
use std::rc::Rc;
cfg_if! {
if #[cfg(feature = "std_web")] {
use crate::html::EventListener;
#[allow(unused_imports)]
use stdweb::{_js_impl, js};
use stdweb::unstable::TryFrom;
use stdweb::web::html_element::{InputElement, TextAreaElement};
use stdweb::web::{Element, IElement, INode};
} else if #[cfg(feature = "web_sys")] {
use gloo::events::EventListener;
use std::ops::Deref;
use wasm_bindgen::JsCast;
use web_sys::{
Element, HtmlInputElement as InputElement, HtmlTextAreaElement as TextAreaElement, HtmlButtonElement
};
}
}
use wasm_bindgen::JsCast;
use web_sys::{
Element, HtmlButtonElement, HtmlInputElement as InputElement,
HtmlTextAreaElement as TextAreaElement,
};
/// SVG namespace string used for creating svg elements
pub const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
@ -249,22 +237,13 @@ impl VTag {
if let Some(element) = self.reference.as_ref() {
if self.element_type == ElementType::Input {
let input_el = cfg_match! {
feature = "std_web" => InputElement::try_from(element.clone()).ok(),
feature = "web_sys" => element.dyn_ref::<InputElement>(),
};
let input_el = element.dyn_ref::<InputElement>();
if let Some(input) = input_el {
let current_value = cfg_match! {
feature = "std_web" => input.raw_value(),
feature = "web_sys" => input.value(),
};
let current_value = input.value();
self.set_value(&current_value)
}
} else if self.element_type == ElementType::Textarea {
let textarea_el = cfg_match! {
feature = "std_web" => TextAreaElement::try_from(element.clone()).ok(),
feature = "web_sys" => element.dyn_ref::<TextAreaElement>(),
};
let textarea_el = element.dyn_ref::<TextAreaElement>();
if let Some(tae) = textarea_el {
let current_value = &tae.value();
self.set_value(&current_value)
@ -331,28 +310,21 @@ impl VTag {
.expect("invalid attribute key");
}
Patch::Remove(key) => {
cfg_match! {
feature = "std_web" => element.remove_attribute(&key),
feature = "web_sys" => element.remove_attribute(&key)
.expect("could not remove attribute"),
};
element
.remove_attribute(&key)
.expect("could not remove attribute");
}
}
}
// TODO: add std_web after https://github.com/koute/stdweb/issues/395 will be approved
// Check this out: https://github.com/yewstack/yew/pull/1033/commits/4b4e958bb1ccac0524eb20f63f06ae394c20553d
#[cfg(feature = "web_sys")]
{
if self.element_type == ElementType::Button {
if let Some(button) = element.dyn_ref::<HtmlButtonElement>() {
if let Some(change) = self.diff_kind(ancestor) {
let kind = match change {
Patch::Add(kind, _) | Patch::Replace(kind, _) => kind,
Patch::Remove(_) => "",
};
button.set_type(kind);
}
if self.element_type == ElementType::Button {
if let Some(button) = element.dyn_ref::<HtmlButtonElement>() {
if let Some(change) = self.diff_kind(ancestor) {
let kind = match change {
Patch::Add(kind, _) | Patch::Replace(kind, _) => kind,
Patch::Remove(_) => "",
};
button.set_type(kind);
}
}
}
@ -362,28 +334,13 @@ impl VTag {
// and useful in templates. For example I interpret `checked`
// attribute as `checked` parameter, not `defaultChecked` as browsers do
if self.element_type == ElementType::Input {
if let Some(input) = {
cfg_match! {
feature = "std_web" => InputElement::try_from(element.clone()).ok(),
feature = "web_sys" => element.dyn_ref::<InputElement>(),
}
} {
if let Some(input) = element.dyn_ref::<InputElement>() {
if let Some(change) = self.diff_kind(ancestor) {
let kind = match change {
Patch::Add(kind, _) | Patch::Replace(kind, _) => kind,
Patch::Remove(_) => "",
};
cfg_match! {
feature = "std_web" => ({
//https://github.com/koute/stdweb/commit/3b85c941db00b8e3c942624afd50c5929085fb08
//input.set_kind(&kind);
let input = &input;
js! { @(no_return)
@{input}.type = @{kind};
}
}),
feature = "web_sys" => input.set_type(kind),
}
input.set_type(kind)
}
if let Some(change) = self.diff_value(ancestor) {
@ -391,10 +348,7 @@ impl VTag {
Patch::Add(kind, _) | Patch::Replace(kind, _) => kind,
Patch::Remove(_) => "",
};
cfg_match! {
feature = "std_web" => input.set_raw_value(raw_value),
feature = "web_sys" => input.set_value(raw_value),
};
input.set_value(raw_value)
}
// IMPORTANT! This parameter has to be set every time
@ -402,12 +356,7 @@ impl VTag {
set_checked(&input, self.checked);
}
} else if self.element_type == ElementType::Textarea {
if let Some(tae) = {
cfg_match! {
feature = "std_web" => TextAreaElement::try_from(element.clone()).ok(),
feature = "web_sys" => element.dyn_ref::<TextAreaElement>(),
}
} {
if let Some(tae) = { element.dyn_ref::<TextAreaElement>() } {
if let Some(change) = self.diff_value(ancestor) {
let value = match change {
Patch::Add(kind, _) | Patch::Replace(kind, _) => kind,
@ -426,10 +375,7 @@ impl VTag {
.namespace_uri()
.map_or(false, |ns| ns == SVG_NAMESPACE)
{
let namespace = cfg_match! {
feature = "std_web" => SVG_NAMESPACE,
feature = "web_sys" => Some(SVG_NAMESPACE),
};
let namespace = Some(SVG_NAMESPACE);
document()
.create_element_ns(namespace, tag)
.expect("can't create namespaced element for vtag")
@ -509,10 +455,7 @@ impl VDiff for VTag {
ancestor_tag.children.detach(element);
}
let node = cfg_match! {
feature = "std_web" => element.as_node(),
feature = "web_sys" => element.deref(),
};
let node = element.deref();
self.node_ref.set(Some(node.clone()));
self.node_ref.clone()
}
@ -520,10 +463,7 @@ impl VDiff for VTag {
/// Set `checked` value for the `InputElement`.
fn set_checked(input: &InputElement, value: bool) {
cfg_match! {
feature = "std_web" => js!( @(no_return) @{input}.checked = @{value}; ),
feature = "web_sys" => input.set_checked(value),
};
input.set_checked(value);
}
impl PartialEq for VTag {
@ -563,8 +503,6 @@ mod tests {
use super::*;
use crate::html;
use std::any::TypeId;
#[cfg(feature = "std_web")]
use stdweb::web::{document, IElement};
#[cfg(feature = "wasm_test")]
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
@ -696,15 +634,11 @@ mod tests {
#[test]
fn supports_svg() {
#[cfg(feature = "std_web")]
let document = document();
#[cfg(feature = "web_sys")]
let document = web_sys::window().unwrap().document().unwrap();
let scope = test_scope();
let div_el = document.create_element("div").unwrap();
let namespace = SVG_NAMESPACE;
#[cfg(feature = "web_sys")]
let namespace = Some(namespace);
let svg_el = document.create_element_ns(namespace, "svg").unwrap();
@ -838,9 +772,6 @@ mod tests {
let scope = test_scope();
let parent = document().create_element("div").unwrap();
#[cfg(feature = "std_web")]
document().body().unwrap().append_child(&parent);
#[cfg(feature = "web_sys")]
document().body().unwrap().append_child(&parent).unwrap();
let mut elem = html! { <div></div> };
@ -855,9 +786,6 @@ mod tests {
let scope = test_scope();
let parent = document().create_element("div").unwrap();
#[cfg(feature = "std_web")]
document().body().unwrap().append_child(&parent);
#[cfg(feature = "web_sys")]
document().body().unwrap().append_child(&parent).unwrap();
let mut elem = html! { <div class="ferris the crab"></div> };
@ -872,9 +800,6 @@ mod tests {
let scope = test_scope();
let parent = document().create_element("div").unwrap();
#[cfg(feature = "std_web")]
document().body().unwrap().append_child(&parent);
#[cfg(feature = "web_sys")]
document().body().unwrap().append_child(&parent).unwrap();
let expected = "not_changed_value";
@ -890,14 +815,8 @@ mod tests {
// User input
let input_ref = vtag.reference.as_ref().unwrap();
let input = cfg_match! {
feature = "std_web" => InputElement::try_from(input_ref.clone()).ok(),
feature = "web_sys" => input_ref.dyn_ref::<InputElement>(),
};
cfg_match! {
feature = "std_web" => input.unwrap().set_raw_value("User input"),
feature = "web_sys" => input.unwrap().set_value("User input"),
};
let input = input_ref.dyn_ref::<InputElement>();
input.unwrap().set_value("User input");
let ancestor = vtag;
let mut elem = html! { <input value=expected /> };
@ -913,16 +832,9 @@ mod tests {
// Get new current value of the input element
let input_ref = vtag.reference.as_ref().unwrap();
let input = cfg_match! {
feature = "std_web" => InputElement::try_from(input_ref.clone()).ok(),
feature = "web_sys" => input_ref.dyn_ref::<InputElement>(),
}
.unwrap();
let input = input_ref.dyn_ref::<InputElement>().unwrap();
let current_value = cfg_match! {
feature = "std_web" => input.raw_value(),
feature = "web_sys" => input.value(),
};
let current_value = input.value();
// check whether not changed virtual dom value has been set to the input element
assert_eq!(current_value, expected);
@ -933,9 +845,6 @@ mod tests {
let scope = test_scope();
let parent = document().create_element("div").unwrap();
#[cfg(feature = "std_web")]
document().body().unwrap().append_child(&parent);
#[cfg(feature = "web_sys")]
document().body().unwrap().append_child(&parent).unwrap();
// Initial state
@ -949,14 +858,8 @@ mod tests {
// User input
let input_ref = vtag.reference.as_ref().unwrap();
let input = cfg_match! {
feature = "std_web" => InputElement::try_from(input_ref.clone()).ok(),
feature = "web_sys" => input_ref.dyn_ref::<InputElement>(),
};
cfg_match! {
feature = "std_web" => input.unwrap().set_raw_value("User input"),
feature = "web_sys" => input.unwrap().set_value("User input"),
};
let input = input_ref.dyn_ref::<InputElement>();
input.unwrap().set_value("User input");
let ancestor = vtag;
let mut elem = html! { <input /> };
@ -972,16 +875,9 @@ mod tests {
// Get user value of the input element
let input_ref = vtag.reference.as_ref().unwrap();
let input = cfg_match! {
feature = "std_web" => InputElement::try_from(input_ref.clone()).ok(),
feature = "web_sys" => input_ref.dyn_ref::<InputElement>(),
}
.unwrap();
let input = input_ref.dyn_ref::<InputElement>().unwrap();
let current_value = cfg_match! {
feature = "std_web" => input.raw_value(),
feature = "web_sys" => input.value(),
};
let current_value = input.value();
// check whether not changed virtual dom value has been set to the input element
assert_eq!(current_value, "User input");
@ -992,9 +888,6 @@ mod tests {
let scope = test_scope();
let parent = document().create_element("div").unwrap();
#[cfg(feature = "std_web")]
document().body().unwrap().append_child(&parent);
#[cfg(feature = "web_sys")]
document().body().unwrap().append_child(&parent).unwrap();
let mut elem = html! { <@{
@ -1008,7 +901,6 @@ mod tests {
// make sure the new tag name is used internally
assert_eq!(vtag.tag(), "a");
#[cfg(feature = "web_sys")]
// Element.tagName is always in the canonical upper-case form.
assert_eq!(vtag.reference.as_ref().unwrap().tag_name(), "A");
}
@ -1049,19 +941,13 @@ mod tests {
let scope = test_scope();
let parent = document().create_element("div").unwrap();
#[cfg(feature = "std_web")]
document().body().unwrap().append_child(&parent);
#[cfg(feature = "web_sys")]
document().body().unwrap().append_child(&parent).unwrap();
let node_ref = NodeRef::default();
let mut elem: VNode = html! { <div ref=node_ref.clone()></div> };
assert_vtag(&mut elem);
elem.apply(&scope, &parent, NodeRef::default(), None);
let parent_node = cfg_match! {
feature = "std_web" => parent.as_node(),
feature = "web_sys" => parent.deref(),
};
let parent_node = parent.deref();
assert_eq!(node_ref.get(), parent_node.first_child());
elem.detach(&parent);
assert!(node_ref.get().is_none());
@ -1094,7 +980,7 @@ mod tests {
}
}
#[cfg(all(test, feature = "web_sys"))]
#[cfg(test)]
mod layout_tests {
extern crate self as yew;

View File

@ -3,17 +3,10 @@
use super::{VDiff, VNode};
use crate::html::{AnyScope, NodeRef};
use crate::utils::document;
use cfg_if::cfg_if;
use log::warn;
use std::borrow::Cow;
use std::cmp::PartialEq;
cfg_if! {
if #[cfg(feature = "std_web")] {
use stdweb::web::{Element, INode, TextNode};
} else if #[cfg(feature = "web_sys")] {
use web_sys::{Element, Text as TextNode};
}
}
use web_sys::{Element, Text as TextNode};
/// A type for a virtual
/// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode)
@ -110,7 +103,7 @@ mod test {
}
}
#[cfg(all(test, feature = "web_sys"))]
#[cfg(test)]
mod layout_tests {
extern crate self as yew;

View File

@ -9,8 +9,8 @@ repository = "https://github.com/yewstack/yew"
readme = "README.md"
[features]
default = ["web_sys", "stable"] # Only stable is included by default.
all = ["web_sys", "stable", "experimental"]
default = ["stable"] # Only stable is included by default.
all = ["stable", "experimental"]
# Broad features
## All features MUST be stable or experimental or soft_depricated
@ -35,14 +35,6 @@ store = []
lrc = []
mrc_irc = []
std_web = [
"yew/std_web",
]
web_sys = [
"yew/web_sys",
"web-sys"
]
[dependencies]
log = "0.4.8"
serde = {version= "1.0.102", optional = true}

View File

@ -1,10 +1,6 @@
//! Feature to enable fetching using web_sys-based fetch requests.
//!
//! This feature makes use of JSON for the request and response bodies.
//!
//! # Note
//! Because this makes use of futures, enabling this feature will require the use of a
//! web_sys compatible build environment and will prevent you from using `cargo web`.
use crate::NeqAssign; // requires "neq" feature.

View File

@ -21,6 +21,9 @@
"concepts/components/callbacks": {
"title": "Callbacks"
},
"concepts/components/children": {
"title": "Children"
},
"concepts/components/properties": {
"title": "Properties"
},
@ -35,10 +38,10 @@
"title": "#[function_component]"
},
"concepts/function-components/custom-hooks": {
"title": "Custom hooks"
"title": "Custom Hooks"
},
"concepts/function-components/pre-defined-hooks": {
"title": "Pre-defined hooks"
"title": "Pre-defined Hooks"
},
"concepts/html": {
"title": "HTML",
@ -75,9 +78,6 @@
"getting-started/build-a-sample-app": {
"title": "Build a sample app"
},
"getting-started/choose-web-library": {
"title": "Choosing a web library"
},
"getting-started/examples": {
"title": "Examples"
},
@ -85,9 +85,6 @@
"title": "Project Setup",
"sidebar_label": "Introduction"
},
"getting-started/project-setup/using-cargo-web": {
"title": "Using cargo-web"
},
"getting-started/project-setup/using-trunk": {
"title": "Using trunk"
},

View File

@ -8,12 +8,10 @@
"ids": [
"getting-started/project-setup",
"getting-started/project-setup/using-trunk",
"getting-started/project-setup/using-wasm-pack",
"getting-started/project-setup/using-cargo-web"
"getting-started/project-setup/using-wasm-pack"
]
},
"getting-started/build-a-sample-app",
"getting-started/choose-web-library",
"getting-started/examples",
"getting-started/starter-templates"
],