Skip to content

Commit

Permalink
Add component and parent access to scope (#1151)
Browse files Browse the repository at this point in the history
* Add reference to parent to scope

* Add untyped scope
  • Loading branch information
jstarry authored Apr 28, 2020
1 parent 404ff77 commit 9c09515
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 82 deletions.
2 changes: 1 addition & 1 deletion yew/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ where
{
/// Creates a new `App` with a component in a context.
pub fn new() -> Self {
let scope = Scope::new();
let scope = Scope::new(None);
App { scope }
}

Expand Down
4 changes: 2 additions & 2 deletions yew/src/html/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ mod listener;
mod scope;

pub use listener::*;
pub use scope::Scope;
pub(crate) use scope::{AnyScope, ComponentUpdate};
pub(crate) use scope::ComponentUpdate;
pub use scope::{AnyScope, Scope};

use crate::callback::Callback;
use crate::virtual_dom::{VChild, VList, VNode};
Expand Down
167 changes: 112 additions & 55 deletions yew/src/html/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ use super::{Callback, Component, NodeRef, Renderable};
use crate::scheduler::{scheduler, ComponentRunnableType, Runnable, Shared};
use crate::virtual_dom::{VDiff, VNode};
use cfg_if::cfg_if;
use std::any::Any;
use std::cell::RefCell;
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")] {
Expand All @@ -24,9 +25,62 @@ pub(crate) enum ComponentUpdate<COMP: Component> {
Properties(COMP::Properties, NodeRef),
}

/// Untyped scope used for accessing parent scope
#[derive(Debug, Clone)]
pub struct AnyScope {
type_id: TypeId,
parent: Option<Rc<AnyScope>>,
state: Rc<dyn Any>,
}

impl Default for AnyScope {
fn default() -> Self {
Self {
type_id: TypeId::of::<()>(),
parent: None,
state: Rc::new(()),
}
}
}

impl<COMP: Component> From<Scope<COMP>> for AnyScope {
fn from(scope: Scope<COMP>) -> Self {
AnyScope {
type_id: TypeId::of::<COMP>(),
parent: scope.parent,
state: Rc::new(scope.state),
}
}
}

impl AnyScope {
/// Returns the parent scope
pub fn get_parent(&self) -> Option<&AnyScope> {
self.parent.as_deref()
}

/// Returns the type of the linked component
pub fn get_type_id(&self) -> &TypeId {
&self.type_id
}

/// Attempts to downcast into a typed scope
pub fn downcast<COMP: Component>(self) -> Scope<COMP> {
Scope {
parent: self.parent,
state: self
.state
.downcast_ref::<Shared<ComponentState<COMP>>>()
.expect("unexpected component type")
.clone(),
}
}
}

/// A context which allows sending messages to a component.
pub struct Scope<COMP: Component> {
shared_state: Shared<ComponentState<COMP>>,
parent: Option<Rc<AnyScope>>,
state: Shared<ComponentState<COMP>>,
}

impl<COMP: Component> fmt::Debug for Scope<COMP> {
Expand All @@ -38,22 +92,30 @@ impl<COMP: Component> fmt::Debug for Scope<COMP> {
impl<COMP: Component> Clone for Scope<COMP> {
fn clone(&self) -> Self {
Scope {
shared_state: self.shared_state.clone(),
parent: self.parent.clone(),
state: self.state.clone(),
}
}
}

impl<COMP: Component> Default for Scope<COMP> {
fn default() -> Self {
Scope::new()
impl<COMP: Component> Scope<COMP> {
/// Returns the parent scope
pub fn get_parent(&self) -> Option<&AnyScope> {
self.parent.as_deref()
}
}

impl<COMP: Component> Scope<COMP> {
/// visible for testing
pub fn new() -> Self {
let shared_state = Rc::new(RefCell::new(ComponentState::Empty));
Scope { shared_state }
/// Returns the linked component if available
pub fn get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
self.state.try_borrow().ok().and_then(|state_ref| {
state_ref.component()?;
Some(Ref::map(state_ref, |this| this.component().unwrap()))
})
}

pub(crate) fn new(parent: Option<AnyScope>) -> Self {
let parent = parent.map(Rc::new);
let state = Rc::new(RefCell::new(ComponentState::Empty));
Scope { parent, state }
}

/// Mounts a component with `props` to the specified `element` in the DOM.
Expand All @@ -72,23 +134,23 @@ impl<COMP: Component> Scope<COMP> {
props,
ancestor,
};
*scope.shared_state.borrow_mut() = ComponentState::Ready(ready_state);
*scope.state.borrow_mut() = ComponentState::Ready(ready_state);
scope.create();
scope
}

/// Schedules a task to create and render a component and then mount it to the DOM
pub(crate) fn create(&mut self) {
let shared_state = self.shared_state.clone();
let create = CreateComponent { shared_state };
let state = self.state.clone();
let create = CreateComponent { state };
scheduler().push_comp(ComponentRunnableType::Create, Box::new(create));
self.rendered(true);
}

/// Schedules a task to send a message or new props to a component
pub(crate) fn update(&self, update: ComponentUpdate<COMP>) {
let update = UpdateComponent {
shared_state: self.shared_state.clone(),
state: self.state.clone(),
update,
};
scheduler().push_comp(ComponentRunnableType::Update, Box::new(update));
Expand All @@ -97,18 +159,18 @@ impl<COMP: Component> Scope<COMP> {

/// Schedules a task to call the rendered method on a component
pub(crate) fn rendered(&self, first_render: bool) {
let shared_state = self.shared_state.clone();
let state = self.state.clone();
let rendered = RenderedComponent {
shared_state,
state,
first_render,
};
scheduler().push_comp(ComponentRunnableType::Rendered, Box::new(rendered));
}

/// Schedules a task to destroy a component
pub(crate) fn destroy(&mut self) {
let shared_state = self.shared_state.clone();
let destroy = DestroyComponent { shared_state };
let state = self.state.clone();
let destroy = DestroyComponent { state };
scheduler().push_comp(ComponentRunnableType::Destroy, Box::new(destroy));
}

Expand Down Expand Up @@ -180,6 +242,15 @@ enum ComponentState<COMP: Component> {
Destroyed,
}

impl<COMP: Component> ComponentState<COMP> {
fn component(&self) -> Option<&COMP> {
match self {
ComponentState::Created(state) => Some(&state.component),
_ => None,
}
}
}

impl<COMP: Component> fmt::Display for ComponentState<COMP> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Expand All @@ -205,10 +276,11 @@ impl<COMP: Component> ReadyState<COMP> {
fn create(self) -> CreatedState<COMP> {
CreatedState {
rendered: false,
component: COMP::create(self.props, self.scope),
component: COMP::create(self.props, self.scope.clone()),
element: self.element,
last_frame: self.ancestor,
node_ref: self.node_ref,
scope: self.scope,
}
}
}
Expand All @@ -219,6 +291,7 @@ struct CreatedState<COMP: Component> {
component: COMP,
last_frame: Option<VNode>,
node_ref: NodeRef,
scope: Scope<COMP>,
}

impl<COMP: Component> CreatedState<COMP> {
Expand All @@ -231,7 +304,12 @@ impl<COMP: Component> CreatedState<COMP> {

fn update(mut self) -> Self {
let mut root = self.component.render();
if let Some(node) = root.apply(&self.element, None, self.last_frame) {
if let Some(node) = root.apply(
&self.scope.clone().into(),
&self.element,
None,
self.last_frame,
) {
self.node_ref.set(Some(node));
} else if let VNode::VComp(child) = &root {
// If the root VNode is a VComp, we won't have access to the rendered DOM node
Expand All @@ -249,7 +327,7 @@ struct RenderedComponent<COMP>
where
COMP: Component,
{
shared_state: Shared<ComponentState<COMP>>,
state: Shared<ComponentState<COMP>>,
first_render: bool,
}

Expand All @@ -258,8 +336,8 @@ where
COMP: Component,
{
fn run(self: Box<Self>) {
let current_state = self.shared_state.replace(ComponentState::Processing);
self.shared_state.replace(match current_state {
let current_state = self.state.replace(ComponentState::Processing);
self.state.replace(match current_state {
ComponentState::Created(s) if !s.rendered => {
ComponentState::Created(s.rendered(self.first_render))
}
Expand All @@ -275,16 +353,16 @@ struct CreateComponent<COMP>
where
COMP: Component,
{
shared_state: Shared<ComponentState<COMP>>,
state: Shared<ComponentState<COMP>>,
}

impl<COMP> Runnable for CreateComponent<COMP>
where
COMP: Component,
{
fn run(self: Box<Self>) {
let current_state = self.shared_state.replace(ComponentState::Processing);
self.shared_state.replace(match current_state {
let current_state = self.state.replace(ComponentState::Processing);
self.state.replace(match current_state {
ComponentState::Ready(s) => ComponentState::Created(s.create().update()),
ComponentState::Created(_) | ComponentState::Destroyed => current_state,
ComponentState::Empty | ComponentState::Processing => {
Expand All @@ -298,15 +376,15 @@ struct DestroyComponent<COMP>
where
COMP: Component,
{
shared_state: Shared<ComponentState<COMP>>,
state: Shared<ComponentState<COMP>>,
}

impl<COMP> Runnable for DestroyComponent<COMP>
where
COMP: Component,
{
fn run(self: Box<Self>) {
match self.shared_state.replace(ComponentState::Destroyed) {
match self.state.replace(ComponentState::Destroyed) {
ComponentState::Created(mut this) => {
this.component.destroy();
if let Some(last_frame) = &mut this.last_frame {
Expand All @@ -328,7 +406,7 @@ struct UpdateComponent<COMP>
where
COMP: Component,
{
shared_state: Shared<ComponentState<COMP>>,
state: Shared<ComponentState<COMP>>,
update: ComponentUpdate<COMP>,
}

Expand All @@ -337,8 +415,8 @@ where
COMP: Component,
{
fn run(self: Box<Self>) {
let current_state = self.shared_state.replace(ComponentState::Processing);
self.shared_state.replace(match current_state {
let current_state = self.state.replace(ComponentState::Processing);
self.state.replace(match current_state {
ComponentState::Created(mut this) => {
let should_update = match self.update {
ComponentUpdate::Message(message) => this.component.update(message),
Expand Down Expand Up @@ -367,24 +445,3 @@ where
});
}
}

pub(crate) struct AnyScope {
scope: Box<dyn Any>,
}

impl<COMP: Component> From<Scope<COMP>> for AnyScope {
fn from(scope: Scope<COMP>) -> Self {
AnyScope {
scope: Box::new(scope),
}
}
}

impl AnyScope {
pub(crate) fn downcast<COMP: Component>(&self) -> Scope<COMP> {
self.scope
.downcast_ref::<Scope<COMP>>()
.expect("INTERNAL: unexpected component type, please report")
.clone()
}
}
2 changes: 2 additions & 0 deletions yew/src/virtual_dom/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod vtag;
#[doc(hidden)]
pub mod vtext;

use crate::html::AnyScope;
use cfg_if::cfg_if;
use indexmap::set::IndexSet;
use std::collections::HashMap;
Expand Down Expand Up @@ -217,6 +218,7 @@ pub(crate) trait VDiff {
/// (always removes the `Node` that exists).
fn apply(
&mut self,
scope: &AnyScope,
parent: &Element,
previous_sibling: Option<&Node>,
ancestor: Option<VNode>,
Expand Down
Loading

0 comments on commit 9c09515

Please sign in to comment.