Reimplement App type based on scope

Add prelude module
This commit is contained in:
Denis Kolodin 2018-01-11 23:50:58 +03:00
parent b5949c349f
commit ecf364173f
8 changed files with 184 additions and 44 deletions

View File

@ -1,4 +1,4 @@
use yew::html::*;
use yew::prelude::*;
use button::Button;
pub struct Barrier {

View File

@ -1,4 +1,4 @@
use yew::html::*;
use yew::prelude::*;
pub struct Button {
title: String,

View File

@ -1,4 +1,4 @@
use yew::html::*;
use yew::prelude::*;
#[derive(PartialEq, Clone)]
pub enum Color {

View File

@ -5,7 +5,7 @@ mod counter;
mod button;
mod barrier;
use yew::html::*;
use yew::prelude::*;
use yew::services::console::ConsoleService;
use counter::{Counter, Color};
use barrier::Barrier;
@ -14,7 +14,7 @@ struct Context {
console: ConsoleService,
}
impl counter::Printer for Context {
impl counter::Printer for AppContext<Context, Model, Msg> {
fn print(&mut self, data: &str) {
self.console.log(data);
}
@ -29,47 +29,40 @@ enum Msg {
ChildClicked(u32),
}
impl Component<Context> for Model {
type Msg = Msg;
type Properties = ();
fn create(_: &mut ScopeRef<Context, Self>) -> Self {
Model { color: Color::Red }
}
fn update(&mut self, msg: Self::Msg, context: &mut ScopeRef<Context, Self>) -> ShouldUpdate {
match msg {
Msg::Repaint => {
self.color = Color::Blue;
true
}
Msg::ChildClicked(value) => {
context.console.log(&format!("child clicked: {}", value));
false
}
fn update(context: &mut Context, model: &mut Model, msg: Msg) -> ShouldUpdate {
match msg {
Msg::Repaint => {
model.color = Color::Blue;
true
}
Msg::ChildClicked(value) => {
context.console.log(&format!("child clicked: {}", value));
false
}
}
}
fn view(&self) -> Html<Context, Self> {
let counter = |_| html! {
<Counter: color=&self.color, onclick=Msg::ChildClicked,/>
};
html! {
<div>
<Barrier: limit=10, onsignal=|_| Msg::Repaint, />
{ for (0..1000).map(counter) }
</div>
}
fn view(model: &Model) -> AppHtml<Context, Model, Msg> {
let counter = |_| html! {
<Counter: color=&model.color, onclick=Msg::ChildClicked,/>
};
html! {
<div>
<Barrier: limit=10, onsignal=|_| Msg::Repaint, />
{ for (0..1000).map(counter) }
</div>
}
}
fn main() {
yew::initialize();
let app = App::new();
let context = Context {
console: ConsoleService,
};
let app: Scope<Context, Model> = Scope::new(context);
app.mount_to_body();
let model = Model {
color: Color::Red,
};
app.mount(context, model, update, view);
yew::run_loop();
}

144
src/app.rs Normal file
View File

@ -0,0 +1,144 @@
use std::rc::Rc;
use std::cell::RefCell;
use stdweb::web::document;
use html::{Component, ComponentUpdate, Html, ScopeBuilder, ScopeSender, ScopeRef, ShouldUpdate};
use std::ops::{Deref, DerefMut};
pub struct App<CTX, MOD, MSG>
where
CTX: 'static,
MOD: 'static,
MSG: 'static,
{
builder: ScopeBuilder<AppContext<CTX, MOD, MSG>, AppImpl<CTX, MOD, MSG>>,
}
pub struct AppSender<CTX, MOD, MSG>
where
CTX: 'static,
MOD: 'static,
MSG: 'static,
{
sender: ScopeSender<AppContext<CTX, MOD, MSG>, AppImpl<CTX, MOD, MSG>>,
}
impl<CTX, MOD, MSG> AppSender<CTX, MOD, MSG> {
pub fn send(&mut self, msg: MSG) {
self.sender.send(ComponentUpdate::Message(msg))
}
}
impl<CTX, MOD, MSG> App<CTX, MOD, MSG> {
pub fn new() -> Self {
App {
builder: ScopeBuilder::new(),
}
}
pub fn sender(&mut self) -> AppSender<CTX, MOD, MSG> {
let sender = self.builder.sender();
AppSender {
sender,
}
}
pub fn mount<U, V>(self, context: CTX, model: MOD, update: U, view: V)
where
U: Fn(&mut CTX, &mut MOD, MSG) -> ShouldUpdate + 'static,
V: Fn(&MOD) -> Html<AppContext<CTX, MOD, MSG>, AppImpl<CTX, MOD, MSG>> + 'static,
{
self.mount_to("body", context, model, update, view)
}
pub fn mount_to<U, V>(self, selector: &str, context: CTX, model: MOD, update: U, view: V)
where
U: Fn(&mut CTX, &mut MOD, MSG) -> ShouldUpdate + 'static,
V: Fn(&MOD) -> Html<AppContext<CTX, MOD, MSG>, AppImpl<CTX, MOD, MSG>> + 'static,
{
let element = document().query_selector(selector)
.expect(format!("can't get node with selector `{}` for rendering", selector).as_str());
let app_impl = AppImpl {
model: model,
update: Box::new(update),
view: Box::new(view),
};
let context_impl = AppContext {
app: Some(app_impl),
context: context,
};
let context = Rc::new(RefCell::new(context_impl));
let scope = self.builder.build(context);
scope.mount(element);
}
}
pub struct AppContext<CTX, MOD, MSG>
where
CTX: 'static,
MOD: 'static,
MSG: 'static,
{
app: Option<AppImpl<CTX, MOD, MSG>>,
context: CTX,
}
impl<CTX, MOD, MSG> AsRef<CTX> for AppContext<CTX, MOD, MSG> {
fn as_ref(&self) -> &CTX {
&self.context
}
}
impl<CTX, MOD, MSG> AsMut<CTX> for AppContext<CTX, MOD, MSG> {
fn as_mut(&mut self) -> &mut CTX {
&mut self.context
}
}
impl<CTX, MOD, MSG> Deref for AppContext<CTX, MOD, MSG> {
type Target = CTX;
fn deref(&self) -> &Self::Target {
&self.context
}
}
impl<CTX, MOD, MSG> DerefMut for AppContext<CTX, MOD, MSG> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.context
}
}
pub struct AppImpl<CTX, MOD, MSG>
where
CTX: 'static,
MOD: 'static,
MSG: 'static,
{
model: MOD,
update: Box<Fn(&mut CTX, &mut MOD, MSG) -> ShouldUpdate>,
view: Box<Fn(&MOD) -> Html<AppContext<CTX, MOD, MSG>, AppImpl<CTX, MOD, MSG>>>,
}
impl<CTX, MOD, MSG> Component<AppContext<CTX, MOD, MSG>> for AppImpl<CTX, MOD, MSG>
where
CTX: 'static,
MOD: 'static,
MSG: 'static,
{
type Msg = MSG;
type Properties = ();
fn create(context: &mut ScopeRef<AppContext<CTX, MOD, MSG>, Self>) -> Self {
context.app.take().expect("tried to unpack app impl twice")
}
fn update(&mut self, msg: Self::Msg, context: &mut ScopeRef<AppContext<CTX, MOD, MSG>, Self>) -> ShouldUpdate {
(self.update)(&mut context.context, &mut self.model, msg)
}
fn view(&self) -> Html<AppContext<CTX, MOD, MSG>, Self> {
(self.view)(&self.model)
}
}
pub type AppHtml<CTX, MOD, MSG> = Html<AppContext<CTX, MOD, MSG>, AppImpl<CTX, MOD, MSG>>;

View File

@ -12,13 +12,6 @@ use stdweb::web::{Element, INode, EventListenerHandle, document};
use stdweb::web::event::{IMouseEvent, IKeyboardEvent};
use virtual_dom::{VNode, Listener};
/// 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");
}
}
/// This type indicates that component should be rendered again.
pub type ShouldUpdate = bool;
@ -89,7 +82,6 @@ impl<IN> Callback<IN> {
pub type SharedContext<CTX> = Rc<RefCell<CTX>>;
/// Local reference to application internals: messages sender and context.
// TODO Rename to Context
pub struct ScopeRef<'a, CTX: 'a, COMP: Component<CTX>> {
context: &'a mut CTX,
tx: &'a mut ComponentSender<CTX, COMP>,
@ -337,6 +329,13 @@ impl<CTX: 'static, COMP: Component<CTX> + 'static> Scope<CTX, COMP> {
}
}
/// 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");
}
}
/// A type which expected as a result of `view` function implementation.
pub type Html<CTX, MSG> = VNode<CTX, MSG>;

View File

@ -58,6 +58,8 @@ extern crate stdweb;
#[macro_use]
pub mod macros;
pub mod html;
pub mod app;
pub mod prelude;
pub mod services;
pub mod format;
pub mod virtual_dom;

2
src/prelude.rs Normal file
View File

@ -0,0 +1,2 @@
pub use html::{Component, Html, ScopeRef, ShouldUpdate, Callback};
pub use app::{App, AppContext, AppHtml};