Skip to content

Commit

Permalink
Merge yewstack#211
Browse files Browse the repository at this point in the history
211: Hide Scope behind App r=DenisKolodin a=DenisKolodin

This PR adds a `Pool` which helps to collect `Callback`s and emit events from a single entity.
That is useful for a context implementation.

Co-authored-by: Denis Kolodin <[email protected]>
  • Loading branch information
bors[bot] and therustmonk committed Apr 20, 2018
2 parents 72fe2e6 + 992c4e2 commit c48a154
Show file tree
Hide file tree
Showing 20 changed files with 206 additions and 131 deletions.
55 changes: 41 additions & 14 deletions examples/build_all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,46 @@ function ctrl_c() {
kill $PID
}

for example in */ ; do
cd $example
cargo update
cargo web build --target wasm32-unknown-emscripten
cd ..
done
function build() {
for example in */ ; do
if [[ $example == server* ]]; then
continue
fi
echo "Building: $example"
cd $example
cargo update
cargo web build --target wasm32-unknown-emscripten
cd ..
done
}

trap ctrl_c INT
function run() {
trap ctrl_c INT
for example in */ ; do
if [[ $example == server* ]]; then
continue
fi
echo "Running: $example"
cd $example
cargo web start --target wasm32-unknown-emscripten &
PID=$!
wait $PID
cd ..
done
}

for example in */ ; do
cd $example
cargo web start --target wasm32-unknown-emscripten &
PID=$!
wait $PID
cd ..
done
case "$1" in
--help)
echo "Available commands: build, run"
;;
build)
build
;;
run)
run
;;
*)
build
run
;;
esac
2 changes: 1 addition & 1 deletion examples/counter/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl AsMut<ConsoleService> for Context {
fn main() {
yew::initialize();
let context = Context {
console: ConsoleService,
console: ConsoleService::new(),
};
let app: App<_, Model> = App::new(context);
app.mount_to_body();
Expand Down
2 changes: 1 addition & 1 deletion examples/crm/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn main() {
yew::initialize();
let context = Context {
storage: StorageService::new(Area::Local),
dialog: DialogService,
dialog: DialogService::new(),
};
let app: App<_, Model> = App::new(context);
app.mount_to_body();
Expand Down
8 changes: 3 additions & 5 deletions examples/custom_components/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
extern crate yew;
extern crate custom_components;

use yew::html::Scope;
use yew::prelude::*;
use yew::services::console::ConsoleService;
use custom_components::{Printer, Model};

Expand All @@ -19,11 +19,9 @@ impl Printer for Context {
fn main() {
yew::initialize();
let context = Context {
console: ConsoleService,
console: ConsoleService::new(),
};
// We use `Scope` here for demonstration.
// You can also use `App` here too.
let app: Scope<Context, Model> = Scope::new(context);
let app: App<Context, Model> = App::new(context);
app.mount_to_body();
yew::run_loop();
}
2 changes: 1 addition & 1 deletion examples/game_of_life/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fn main() {
let context = Context {
interval: IntervalService::new(),
};
let mut app: App<_, GameOfLife> = App::new(context);
let app: App<_, GameOfLife> = App::new(context);

// Send initial message. For demo purposes only!
// You should prefer to initialize everything in `Component::create` implementation.
Expand Down
2 changes: 1 addition & 1 deletion examples/inner_html/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl AsMut<ConsoleService> for Context {
fn main() {
yew::initialize();
let context = Context {
console: ConsoleService,
console: ConsoleService::new(),
};
let app: App<_, Model> = App::new(context);
app.mount_to_body();
Expand Down
2 changes: 1 addition & 1 deletion examples/npm_and_rest/src/gravatar.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use failure::Error;
use yew::format::{Nothing, Json};
use yew::services::fetch::{FetchService, FetchTask, Request, Response};
use yew::html::Callback;
use yew::callback::Callback;

#[derive(Deserialize, Debug)]
pub struct Profile {
Expand Down
13 changes: 11 additions & 2 deletions examples/timer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ use std::time::Duration;
use yew::prelude::*;
use yew::services::Task;
use yew::services::timeout::TimeoutService;
use yew::services::interval::IntervalService;
use yew::services::interval::{IntervalService, IntervalTask};
use yew::services::console::ConsoleService;

pub struct Model {
job: Option<Box<Task>>,
messages: Vec<&'static str>,
_standalone: IntervalTask,
}

pub enum Msg {
Expand All @@ -28,10 +29,18 @@ where
type Msg = Msg;
type Properties = ();

fn create(_: Self::Properties, _: &mut Env<CTX, Self>) -> Self {
fn create(_: Self::Properties, context: &mut Env<CTX, Self>) -> Self {
// This callback doesn't send any message to a scope
let callback = |_| {
println!("Example of a standalone callback.");
};
let interval: &mut IntervalService = context.as_mut();
let handle = interval.spawn(Duration::from_secs(10), callback.into());

Model {
job: None,
messages: Vec::new(),
_standalone: handle,
}
}

Expand Down
2 changes: 1 addition & 1 deletion examples/timer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ fn main() {
let context = Context {
interval: IntervalService::new(),
timeout: TimeoutService::new(),
console: ConsoleService,
console: ConsoleService::new(),
};
let app: App<_, Model> = App::new(context);
app.mount_to_body();
Expand Down
9 changes: 4 additions & 5 deletions examples/two_apps/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ extern crate two_apps;
use std::rc::Rc;
use std::cell::RefCell;
use stdweb::web::{IParentNode, document};
// Use `html` module directly. No use `App`.
use yew::html::*;
use yew::prelude::*;
use two_apps::{Context, Model};

fn mount_app(selector: &'static str, app: Scope<Context, Model>) {
fn mount_app(selector: &'static str, app: App<Context, Model>) {
let element = document().query_selector(selector).unwrap().unwrap();
app.mount(element);
}
Expand All @@ -22,11 +21,11 @@ fn main() {
// Example how to reuse context in two scopes
let context = Rc::new(RefCell::new(context));

let mut first_app = Scope::reuse(context.clone());
let first_app = App::reuse(context.clone());
let to_first = first_app.get_env().sender();
context.borrow_mut().senders.push(to_first);

let mut second_app = Scope::reuse(context.clone());
let second_app = App::reuse(context.clone());
let to_second = second_app.get_env().sender();
context.borrow_mut().senders.push(to_second);

Expand Down
71 changes: 71 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//! This module contains `App` sctruct which used to bootstrap
//! a component in an isolated scope.
use std::rc::Rc;
use std::cell::RefCell;
use stdweb::web::{document, Element, INode, IParentNode};
use html::{Scope, ScopeBuilder, ScopeEnv, Component, Renderable, SharedContext};

/// An application instance.
pub struct App<CTX, COMP: Component<CTX>> {
/// `Scope` holder
scope: Option<Scope<CTX, COMP>>,
/// Environment of the created scope
env: ScopeEnv<CTX, COMP>,
}

impl<CTX, COMP> App<CTX, COMP>
where
CTX: 'static,
COMP: Component<CTX> + Renderable<CTX, COMP>,
{
/// Creates a new `App` with a component in a context.
pub fn new(context: CTX) -> Self {
let context = Rc::new(RefCell::new(context));
App::reuse(context)
}

/// Creates isolated `App` instance, but reuse the context.
pub fn reuse(context: SharedContext<CTX>) -> Self {
let builder = ScopeBuilder::new();
let scope = builder.build(context);
let env = scope.get_env();
App {
scope: Some(scope),
env,
}
}

/// Alias to `mount("body", ...)`.
pub fn mount_to_body(self) {
let element = document()
.query_selector("body")
.expect("can't get body node for rendering")
.expect("can't unwrap body node");
self.mount(element)
}

/// The main entrypoint of a yew program. It works similar as `program`
/// function in Elm. You should provide an initial model, `update` function
/// which will update the state of the model and a `view` function which
/// will render the model to a virtual DOM tree.
pub fn mount(mut self, element: Element) {
clear_element(&element);
self.scope.take()
.expect("can't mount the same app twice")
.mount_in_place(element, None, None, None)
}

/// Returns an environment.
pub fn get_env(&self) -> ScopeEnv<CTX, COMP> {
self.env.clone()
}
}

/// Removes anything from the given element.
fn clear_element(element: &Element) {
while let Some(child) = element.last_child() {
element.remove_child(&child).expect("can't remove a child");
}
}

53 changes: 53 additions & 0 deletions src/callback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! This module contains structs to interact with `Scope`s.
use std::rc::Rc;

/// Universal callback wrapper.
/// <aside class="warning">
/// Use callbacks carefully, because it you call it from `update` loop
/// of `Components` (even from JS) it will delay a message until next.
/// Callbacks should be used from JS callbacks or `setTimeout` calls.
/// </aside>
/// `Rc` wrapper used to make it clonable.
#[must_use]
pub struct Callback<IN>(Rc<Fn(IN)>);

impl<IN, F: Fn(IN) + 'static> From<F> for Callback<IN> {
fn from(func: F) -> Self {
Callback(Rc::new(func))
}
}

impl<IN> Clone for Callback<IN> {
fn clone(&self) -> Self {
Callback(self.0.clone())
}
}

impl<IN> PartialEq for Callback<IN> {
fn eq(&self, other: &Callback<IN>) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}

impl<IN> Callback<IN> {
/// This method calls the actual callback.
pub fn emit(&self, value: IN) {
(self.0)(value);
}
}

impl<IN: 'static> Callback<IN> {
/// Changes input type of the callback to another.
/// Works like common `map` method but in an opposite direction.
pub fn reform<F, T>(self, func: F) -> Callback<T>
where
F: Fn(T) -> IN + 'static,
{
let func = move |input| {
let output = func(input);
self.clone().emit(output);
};
Callback::from(func)
}
}
Loading

0 comments on commit c48a154

Please sign in to comment.