Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change the app struct to be a real handle to an Yew app instance and make it possible to destroy a running app #1825

Merged
merged 38 commits into from
May 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
59d3ee4
Naive implementation of destroy app
nicklaswj Apr 26, 2021
b6ad5ea
Move functions get_component_link and destroy
nicklaswj Apr 26, 2021
e9c8d6c
Change the fn signatures for the other mount fns too
nicklaswj Apr 26, 2021
3b3a387
Add example of how to use the apps dynamically
nicklaswj Apr 27, 2021
c98f936
Rename example
nicklaswj Apr 27, 2021
db2ad55
Change title of example html file
nicklaswj Apr 27, 2021
d493caf
Update examples/dyn_create_destroy_apps/src/counter.rs
nicklaswj May 6, 2021
0139425
Change fn signatures of App to not take Self
nicklaswj May 6, 2021
66ada30
Fix examples to compile with new App signature
nicklaswj May 6, 2021
039723f
Fix tests to compile with new App signature
nicklaswj May 6, 2021
f4ff646
Remove pub pulic App::mount* API
nicklaswj May 6, 2021
28b5894
Make the documentation compile again with the new start_app API
nicklaswj May 6, 2021
ecaeff3
Make the examples compile again with the new start_app API
nicklaswj May 6, 2021
b94ed23
Make the yew packages compile again with the new start_app API
nicklaswj May 6, 2021
b1aa100
rename module yew::app to yew::app_handle
nicklaswj May 6, 2021
1d440de
Fix identation in styling file for dyn_create_destroy_app ex
nicklaswj May 6, 2021
95677dc
Fix naming in examples/dyn_create_destroy_apps/README.md
nicklaswj May 17, 2021
85143b3
Use Self instead of AppHandle to create AppHandle
nicklaswj May 17, 2021
06fc2fd
Fix the start_app_in_body docs
nicklaswj May 17, 2021
482461a
Remove comparison with Elm in the start_app docs
nicklaswj May 17, 2021
7a757b2
Fix english in dyn_create_destroy_apps example
nicklaswj May 17, 2021
9614a25
Impl Deref instead of AsRef for AppHandle
nicklaswj May 17, 2021
807128a
Formatting
nicklaswj May 17, 2021
97e4897
Remove AppHandle::new use Default trait instead
nicklaswj May 17, 2021
18ec2d3
Rename the Yew::start_app* function names
nicklaswj May 17, 2021
a843328
Revert "Remove AppHandle::new use Default trait instead"
nicklaswj May 17, 2021
c341211
remove default impl for AppHandle
nicklaswj May 17, 2021
5906f7e
Make the tests compile again
nicklaswj May 17, 2021
d49e291
Make the examples compile again
nicklaswj May 17, 2021
649bbeb
Add dyn_create_destroy_apps to examples/README.md
nicklaswj May 17, 2021
a7ff6f8
Remove mount_to_body_with_props
nicklaswj May 17, 2021
403f8a7
Use yew getters instead of query selectors
nicklaswj May 17, 2021
6e543d1
Remove AppHandle::new
nicklaswj May 17, 2021
504dad8
Remove unused function
nicklaswj May 17, 2021
286c2af
code style fix
nicklaswj May 17, 2021
b600893
Fix compile error
nicklaswj May 17, 2021
0029c82
Add func set_custom_panic_hook
nicklaswj May 17, 2021
fb03dae
Fix docs for set_custom_panic_hook
nicklaswj May 17, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ members = [
"examples/counter",
"examples/crm",
"examples/dashboard",
"examples/dyn_create_destroy_apps",
"examples/file_upload",
"examples/futures",
"examples/game_of_life",
Expand Down
47 changes: 24 additions & 23 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,30 @@ As an example, check out the TodoMVC example here: <https://examples.yew.rs/todo

## List of examples

| Example | Description |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| [boids](boids) | Yew port of [Boids](https://en.wikipedia.org/wiki/Boids) |
| [counter](counter) | Simple counter which can be incremented and decremented |
| [crm](crm) | Shallow customer relationship management tool |
| [dashboard](dashboard) | Uses the `fetch` and `websocket` services to load external data |
| [file_upload](file_upload) | Uses the `reader` service to read the content of user uploaded files |
| [futures](futures) | Demonstrates how you can use futures and async code with Yew. Features a Markdown renderer. |
| [game_of_life](game_of_life) | Implementation of [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) |
| [inner_html](inner_html) | Embeds an external document as raw HTML by manually managing the element |
| [js_callback](js_callback) | Interacts with JavaScript code |
| [keyed_list](keyed_list) | Demonstrates how to use keys to improve the performance of lists |
| [mount_point](mount_point) | Shows how to mount the root component to a custom element |
| [multi_thread](multi_thread) | Demonstrates the use of Web Workers to offload computation to the background |
| [nested_list](nested_list) | Renders a styled list which tracks hover events |
| [node_refs](node_refs) | Uses a [`NodeRef`](https://yew.rs/docs/concepts/components/refs) to focus the input element under the cursor |
| [pub_sub](pub_sub) | Cross-component communication using [Agents](https://yew.rs/docs/concepts/agents) |
| [router](router) | The best yew blog built with `yew-router` |
| [store](store) | Showcases the `yewtil::store` API |
| [timer](timer) | Demonstrates the use of the interval and timeout services |
| [todomvc](todomvc) | Implementation of [TodoMVC](http://todomvc.com/) |
| [two_apps](two_apps) | Runs two separate Yew apps which can communicate with each other |
| [webgl](webgl) | Controls a [WebGL canvas](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL) from Yew |
| Example | Description |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| [boids](boids) | Yew port of [Boids](https://en.wikipedia.org/wiki/Boids) |
| [counter](counter) | Simple counter which can be incremented and decremented |
| [crm](crm) | Shallow customer relationship management tool |
| [dashboard](dashboard) | Uses the `fetch` and `websocket` services to load external data |
| [dyn_create_destroy_apps](dyn_create_destroy_apps) | Uses the function `start_app_in_element` and the `AppHandle` struct to dynamically create and delete Yew apps |
| [file_upload](file_upload) | Uses the `reader` service to read the content of user uploaded files |
| [futures](futures) | Demonstrates how you can use futures and async code with Yew. Features a Markdown renderer. |
| [game_of_life](game_of_life) | Implementation of [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) |
| [inner_html](inner_html) | Embeds an external document as raw HTML by manually managing the element |
| [js_callback](js_callback) | Interacts with JavaScript code |
| [keyed_list](keyed_list) | Demonstrates how to use keys to improve the performance of lists |
| [mount_point](mount_point) | Shows how to mount the root component to a custom element |
| [multi_thread](multi_thread) | Demonstrates the use of Web Workers to offload computation to the background |
| [nested_list](nested_list) | Renders a styled list which tracks hover events |
| [node_refs](node_refs) | Uses a [`NodeRef`](https://yew.rs/docs/concepts/components/refs) to focus the input element under the cursor |
| [pub_sub](pub_sub) | Cross-component communication using [Agents](https://yew.rs/docs/concepts/agents) |
| [router](router) | The best yew blog built with `yew-router` |
| [store](store) | Showcases the `yewtil::store` API |
| [timer](timer) | Demonstrates the use of the interval and timeout services |
| [todomvc](todomvc) | Implementation of [TodoMVC](http://todomvc.com/) |
| [two_apps](two_apps) | Runs two separate Yew apps which can communicate with each other |
| [webgl](webgl) | Controls a [WebGL canvas](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL) from Yew |

## Next steps

Expand Down
22 changes: 22 additions & 0 deletions examples/dyn_create_destroy_apps/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "dyn_create_destroy_apps"
version = "0.1.0"
authors = ["Nicklas Warming Jacobsen <[email protected]>"]
edition = "2018"
license = "MIT OR Apache-2.0"

[dependencies]
js-sys = "0.3"
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }
slab = "0.4.3"


[dependencies.web-sys]
version = "0.3.50"
features = [
"Document",
"Element",
"Node",
"DomTokenList"
]
21 changes: 21 additions & 0 deletions examples/dyn_create_destroy_apps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Dynamic app creation and destruction example
nicklaswj marked this conversation as resolved.
Show resolved Hide resolved

An example of how to create and destroy Yew apps on demand.

## Running

Run a debug version of this application:

```bash
trunk serve
```

Run a release version of this application:

```bash
trunk serve --release
```

## Concepts

Demonstrates the use of the Yew app handle by dynamically creating and destroying apps.
11 changes: 11 additions & 0 deletions examples/dyn_create_destroy_apps/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Yew • Create and destroy apps</title>

<link data-trunk rel="sass" href="index.scss" />
</head>

<body></body>
</html>
27 changes: 27 additions & 0 deletions examples/dyn_create_destroy_apps/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
button {
border: 0;
color: white;
padding: 14px 14px;
text-align: center;
font-size: 16px;
}

button.create {
background-color: #008f53; /* Green */
width: 200px;
}

button.destroy {
background-color: #ff1f1f; /* Red */
}

.counter {
color: #008f53;
font-size: 48px;
text-align: center;
}

.panel {
display: flex;
justify-content: center;
}
78 changes: 78 additions & 0 deletions examples/dyn_create_destroy_apps/src/counter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use std::time::Duration;
use yew::prelude::*;
use yew_services::{
interval::{IntervalService, IntervalTask},
ConsoleService,
};

pub struct CounterModel {
counter: usize,
props: CounterProps,
_interval_task: IntervalTask,
}

#[derive(Clone, Properties)]
pub struct CounterProps {
pub destroy_callback: Callback<()>,
}

pub enum CounterMessage {
Tick,
}

impl Component for CounterModel {
type Message = CounterMessage;

type Properties = CounterProps;

fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
// Create a Tick message every second
let interval_task = IntervalService::spawn(
Duration::from_secs(1),
link.callback(|()| Self::Message::Tick),
);
Self {
counter: 0,
props,
_interval_task: interval_task,
}
}

fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
// Count our internal state up by one
Self::Message::Tick => {
self.counter += 1;
true
}
}
}

fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}

fn view(&self) -> Html {
let destroy_callback = self.props.destroy_callback.clone();

html! {
<>
// Display the current value of the counter
<p class="counter">
{ "App has lived for " }
{ self.counter }
{ " ticks" }
</p>

// Add button to send a destroy command to the parent app
<button class="destroy" onclick=Callback::from(move |_| destroy_callback.emit(()))>
{ "Destroy this app" }
</button>
</>
}
}

fn destroy(&mut self) {
ConsoleService::log("CounterModel app destroyed");
}
}
117 changes: 117 additions & 0 deletions examples/dyn_create_destroy_apps/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use slab::Slab;
use web_sys::Element;
use yew::prelude::*;
use yew::utils::document;

mod counter;

use counter::{CounterModel, CounterProps};

// Define the possible messages which can be sent to the component
pub enum Msg {
// Spawns a new instance of the CounterModel app
SpawnCounterAppInstance,
// Destroys an instance of a CounterModel app
DestroyCounterApp(usize),
}

pub struct Model {
link: ComponentLink<Self>,
apps: Slab<(Element, AppHandle<CounterModel>)>, // Contains the spawned apps and their parent div elements
apps_container_ref: NodeRef,
}

impl Component for Model {
type Message = Msg;
type Properties = ();

fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
Self {
link,
apps: Slab::new(),
apps_container_ref: NodeRef::default(),
}
}

fn update(&mut self, msg: Self::Message) -> ShouldRender {
let app_container = self
.apps_container_ref
.cast::<Element>()
.expect("Failed to cast app container div to HTMLElement");

match msg {
Msg::SpawnCounterAppInstance => {
// Create a new <div> HtmlElement where the new app will live
let app_div = document()
.create_element("div")
.expect("Failed to create <div> element");

// Append the div to the document body
let _ = app_container
.append_child(&app_div)
.expect("Failed to append app div app container div");

// Reserve an entry for the new app
let app_entry = self.apps.vacant_entry();

// Get the key for the entry and create and mount a new CounterModel app
// with a callback that destroys the app when emitted
let app_key = app_entry.key();
let new_counter_app = yew::start_app_with_props_in_element(
app_div.clone(),
CounterProps {
destroy_callback: self
.link
.callback(move |_| Msg::DestroyCounterApp(app_key)),
},
);

// Insert the app and the app div to our app collection
app_entry.insert((app_div, new_counter_app));
}
Msg::DestroyCounterApp(app_id) => {
// Get the app from the app slabmap
let (app_div, app) = self.apps.remove(app_id);

// Destroy the app
app.destroy();

// Remove the app div from the DOM
app_div.remove()
}
}

// Never render
false
nicklaswj marked this conversation as resolved.
Show resolved Hide resolved
}

fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}

fn view(&self) -> Html {
// We will only render once, and then do the rest of the DOM changes
// by mounting/destroying appinstances of CounterModel
html! {
<>
<div class="panel">
// Create button to create a new app
<button
class="create"
onclick=self.link.callback(|_| Msg::SpawnCounterAppInstance)
>
{ "Spawn new CounterModel app" }
</button>
</div>
// Create a container for all the app instances
<div ref=self.apps_container_ref.clone()>
</div>
</>
}
}
}

fn main() {
// Start main app
yew::start_app::<Model>();
}
2 changes: 1 addition & 1 deletion examples/mount_point/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,5 @@ fn main() {

body.append_child(&mount_point).unwrap();

yew::App::<Model>::new().mount(mount_point);
yew::start_app_in_element::<Model>(mount_point);
}
17 changes: 8 additions & 9 deletions examples/two_apps/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use yew::{html, App, Component, ComponentLink, Html, ShouldRender};
use yew::{html, AppHandle, Component, ComponentLink, Html, ShouldRender};

pub enum Msg {
SetOpposite(ComponentLink<Model>),
Expand Down Expand Up @@ -74,17 +74,16 @@ impl Component for Model {
}
}

fn mount_app(selector: &'static str, app: App<Model>) -> ComponentLink<Model> {
fn mount_app(selector: &'static str) -> AppHandle<Model> {
let document = yew::utils::document();
let element = document.query_selector(selector).unwrap().unwrap();
app.mount(element)
yew::start_app_in_element(element)
}

fn main() {
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));
let first_app = mount_app(".first-app");
let second_app = mount_app(".second-app");

first_app.send_message(Msg::SetOpposite(second_app.clone()));
second_app.send_message(Msg::SetOpposite(first_app.clone()));
}
Loading