Skip to content

Commit

Permalink
cleanup: registering templates, patch the app template upon mounting …
Browse files Browse the repository at this point in the history
…to the mount_node
  • Loading branch information
ivanceras committed Mar 9, 2024
1 parent 92266e7 commit 632a287
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 43 deletions.
21 changes: 11 additions & 10 deletions crates/core/src/dom/component/stateful_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ thread_local! {
static TEMPLATE_LOOKUP: RefCell<HashMap<TypeId, web_sys::Node>> = RefCell::new(HashMap::new());
}

pub fn register_template<APP, MSG>(app: &APP) -> (web_sys::Node, vdom::Node<MSG>)
pub fn register_template<APP, MSG>(app: &APP) -> web_sys::Node
where
APP: Application<MSG>,
MSG: 'static,
{
let type_id = TypeId::of::<APP>();
let view = app.view();
let vdom_template = template::build_vdom_template(&view);
//let vdom_template = template::build_vdom_template(&view);
let template = TEMPLATE_LOOKUP.with_borrow_mut(|map| {
if let Some(existing) = map.get(&type_id) {
existing.clone_node_with_deep(true).expect("deep clone")
Expand All @@ -42,11 +42,7 @@ where
template
}
});
if cfg!(feature = "use-template") {
(template, vdom_template)
} else {
(template, view)
}
template
}

/// A component that can be used directly in the view without mapping
Expand Down Expand Up @@ -131,16 +127,21 @@ where
// The attribute(minus events) however can be used for configurations, for setting initial state
// of the stateful component.

let (_template, vdom_template) = register_template(&app);
let app_view = app.view();
let vdom_template = template::build_vdom_template(&app_view);
#[cfg(feature = "use-template")]
let template = register_template(&app);

let app = Rc::new(RefCell::new(app));

let program = Program {
app_context: AppContext {
app: Rc::clone(&app),
#[cfg(feature = "use-template")]
template: _template,
current_vdom: Rc::new(RefCell::new(vdom_template)),
template: template,
#[cfg(feature = "use-template")]
vdom_template: Rc::new(vdom_template),
current_vdom: Rc::new(RefCell::new(app_view)),
pending_msgs: Rc::new(RefCell::new(VecDeque::new())),
pending_cmds: Rc::new(RefCell::new(VecDeque::new())),
},
Expand Down
40 changes: 31 additions & 9 deletions crates/core/src/dom/dom_patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ where
closure
}
/// get the real DOM target node and make a DomPatch object for each of the Patch
pub(crate) fn convert_patches(&self, patches: &[Patch<MSG>]) -> Result<Vec<DomPatch>, JsValue> {
pub(crate) fn convert_patches(&self, root_node: &web_sys::Node, patches: &[Patch<MSG>]) -> Result<Vec<DomPatch>, JsValue> {
let nodes_to_find: Vec<(&TreePath, Option<&&'static str>)> = patches
.iter()
.map(|patch| (patch.path(), patch.tag()))
Expand All @@ -130,11 +130,7 @@ where
)
.collect();

let nodes_lookup = find_all_nodes(
self.root_node
.borrow()
.as_ref()
.expect("must have a root node"),
let nodes_lookup = find_all_nodes(root_node,
&nodes_to_find,
);

Expand Down Expand Up @@ -277,7 +273,23 @@ where
}
}

pub(crate) fn apply_dom_patch(&mut self, dom_patch: DomPatch) -> Result<(), JsValue> {

/// TODO: this should not have access to root_node, so it can generically
/// apply patch to any dom node
pub(crate) fn apply_dom_patches(&self, root_node: &web_sys::Node, dom_patches: impl IntoIterator<Item = DomPatch>) -> Result<Option<Node>, JsValue> {
let mut new_root_node = None;
for dom_patch in dom_patches {
if let Some(replacement_node) = self.apply_dom_patch(root_node, dom_patch)? {
new_root_node = Some(replacement_node);
}
}
Ok(new_root_node)
}

/// apply a dom patch to this root node,
/// return a new root_node if it would replace the original root_node
/// TODO: this should have no access to root_node, so it can be used in general sense
pub(crate) fn apply_dom_patch(&self, root_node: &web_sys::Node, dom_patch: DomPatch) -> Result<Option<Node>, JsValue> {
let DomPatch {
patch_path,
target_element,
Expand All @@ -297,6 +309,7 @@ where
} else {
panic!("unable to get parent node of the target element: {target_element:?} for patching: {nodes:#?}");
}
Ok(None)
}

PatchVariant::InsertAfterNode { nodes } => {
Expand All @@ -310,6 +323,7 @@ where
.expect("must insert after the target element");
Self::dispatch_mount_event(&for_insert);
}
Ok(None)
}
PatchVariant::AppendChildren { children } => {
for child in children.into_iter() {
Expand All @@ -318,10 +332,12 @@ where
&child,
);
}
Ok(None)
}

PatchVariant::AddAttributes { attrs } => {
self.set_element_dom_attrs(&target_element, attrs);
Ok(None)
}
PatchVariant::RemoveAttributes { attrs } => {
for attr in attrs.iter() {
Expand All @@ -346,6 +362,7 @@ where
}
}
}
Ok(None)
}

// This also removes the associated closures and event listeners to the node being replaced
Expand Down Expand Up @@ -402,9 +419,12 @@ where
// we replace the root node here, so that's reference is updated
// to the newly created node
if patch_path.path.is_empty() {
*self.root_node.borrow_mut() = Some(first_node);
//*self.root_node.borrow_mut() = Some(first_node);
#[cfg(feature = "with-debug")]
log::info!("the root_node is replaced with {:?}", &self.root_node);
Ok(Some(first_node))
}else{
Ok(None)
}
}
PatchVariant::RemoveNode => {
Expand All @@ -417,6 +437,7 @@ where
if target_element.node_type() == Node::ELEMENT_NODE {
self.remove_event_listeners_recursive(&target_element)?;
}
Ok(None)
}
PatchVariant::MoveBeforeNode { for_moving } => {
if let Some(target_parent) = target_element.parent_node() {
Expand All @@ -434,6 +455,7 @@ where
} else {
panic!("unable to get the parent node of the target element");
}
Ok(None)
}

PatchVariant::MoveAfterNode { for_moving } => {
Expand All @@ -451,8 +473,8 @@ where
.insert_adjacent_element(intern("afterend"), to_move_element)
.expect("must insert before this node");
}
Ok(None)
}
}
Ok(())
}
}
49 changes: 40 additions & 9 deletions crates/core/src/dom/program.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::dom::component::register_template;
use crate::dom::template;
use crate::dom::program::app_context::WeakContext;
#[cfg(feature = "with-raf")]
use crate::dom::request_animation_frame;
Expand Down Expand Up @@ -210,9 +211,20 @@ where
/// Create an Rc wrapped instance of program, initializing DomUpdater with the initial view
/// and root node, but doesn't mount it yet.
pub fn new(app: APP) -> Self {
let (template, vdom_template) = register_template(&app);
Program {
app_context: AppContext::new(app, template, vdom_template),
let template = register_template(&app);
let app_view = app.view();
let vdom_template = template::build_vdom_template(&app_view);
let program = Program {
app_context: AppContext {
app: Rc::new(RefCell::new(app)),
#[cfg(feature = "use-template")]
template: template,
#[cfg(feature = "use-template")]
vdom_template: Rc::new(vdom_template),
current_vdom: Rc::new(RefCell::new(app_view)),
pending_msgs: Rc::new(RefCell::new(VecDeque::new())),
pending_cmds: Rc::new(RefCell::new(VecDeque::new())),
},
root_node: Rc::new(RefCell::new(None)),
mount_node: Rc::new(RefCell::new(None)),
node_closures: Rc::new(RefCell::new(ActiveClosure::new())),
Expand All @@ -222,7 +234,8 @@ where
event_closures: Rc::new(RefCell::new(vec![])),
closures: Rc::new(RefCell::new(vec![])),
last_update: Rc::new(RefCell::new(None)),
}
};
program
}

/// executed after the program has been mounted
Expand All @@ -241,7 +254,7 @@ where

// first dispatch call to ensure the template is patched with the
// new app real view
self.dispatch_multiple([]);
//self.dispatch_multiple([]);
}

fn app_hash() -> u64 {
Expand Down Expand Up @@ -372,11 +385,25 @@ where

//TODO: use the template here and append to the mount_node
// NOTE: the template has no attached event
// create a dom patch to the template and apply it first
#[cfg(feature = "use-template")]
let created_node = self.app_context.template.clone();
#[cfg(not(feature = "use-template"))]
let created_node = self.create_dom_node(&self.app_context.current_vdom());

#[cfg(feature = "use-template")]
{
let app_view = self.app_context.app.borrow().view();
let template = self.app_context.template.clone();
let vdom_template = &self.app_context.vdom_template;
let patches = diff(vdom_template, &app_view);
let dom_patches = self.convert_patches(&template, &patches).expect("convert patches");
log::info!("first time patches {}: {patches:#?}", patches.len());
let new_template_node = self.apply_dom_patches(&created_node, dom_patches).expect("template patching");
log::info!("new template node: {:?}", new_template_node);
}


let mount_node: web_sys::Node = match mount_procedure.target {
MountTarget::MountNode => self
.mount_node
Expand Down Expand Up @@ -588,7 +615,12 @@ where
log::debug!("patches: {patches:#?}");
}

self.convert_patches(&patches)
self.convert_patches(
self.root_node
.borrow()
.as_ref()
.expect("must have a root node"),
&patches)
.expect("must convert patches")
}

Expand All @@ -610,9 +642,8 @@ where
return Ok(());
}
let dom_patches: Vec<DomPatch> = self.pending_patches.borrow_mut().drain(..).collect();
for dom_patch in dom_patches {
self.apply_dom_patch(dom_patch)
.expect("must apply dom patch");
if let Some(new_root_node) = self.apply_dom_patches(self.root_node.borrow().as_ref().expect("must have a root node"), dom_patches)?{
*self.root_node.borrow_mut() = Some(new_root_node);
}
Ok(())
}
Expand Down
30 changes: 18 additions & 12 deletions crates/core/src/dom/program/app_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,18 @@ where
/// holds the user application
pub(crate) app: Rc<RefCell<APP>>,

/// the template for this App
/// the dom template for this App
/// This also doesn't change throughout the app lifecycle
#[cfg(feature = "use-template")]
pub(crate) template: web_sys::Node,

/// The vdom template generated from the APP
/// This doesn't change throughout the app lifecycle
#[cfg(feature = "use-template")]
pub(crate) vdom_template: Rc<vdom::Node<MSG>>,

/// the current vdom representation
/// if the dom is sync with the app state the current_vdom corresponds to the app.view
pub(crate) current_vdom: Rc<RefCell<vdom::Node<MSG>>>,

/// The MSG that hasn't been applied to the APP yet
Expand All @@ -42,6 +49,8 @@ where
pub(crate) app: Weak<RefCell<APP>>,
#[cfg(feature = "use-template")]
pub(crate) template: web_sys::Node,
#[cfg(feature = "use-template")]
pub(crate) vdom_template: Weak<vdom::Node<MSG>>,
pub(crate) current_vdom: Weak<RefCell<vdom::Node<MSG>>>,
pub(crate) pending_msgs: Weak<RefCell<VecDeque<MSG>>>,
pub(crate) pending_cmds: Weak<RefCell<VecDeque<Cmd<APP, MSG>>>>,
Expand All @@ -60,6 +69,8 @@ where
app,
#[cfg(feature = "use-template")]
template: self.template.clone(),
#[cfg(feature = "use-template")]
vdom_template: self.vdom_template.upgrade()?,
current_vdom,
pending_msgs,
pending_cmds,
Expand All @@ -76,6 +87,8 @@ where
app: Weak::clone(&self.app),
#[cfg(feature = "use-template")]
template: self.template.clone(),
#[cfg(feature = "use-template")]
vdom_template: Weak::clone(&self.vdom_template),
current_vdom: Weak::clone(&self.current_vdom),
pending_msgs: Weak::clone(&self.pending_msgs),
pending_cmds: Weak::clone(&self.pending_cmds),
Expand All @@ -92,6 +105,8 @@ where
app: Rc::downgrade(&this.app),
#[cfg(feature = "use-template")]
template: this.template.clone(),
#[cfg(feature = "use-template")]
vdom_template: Rc::downgrade(&this.vdom_template),
current_vdom: Rc::downgrade(&this.current_vdom),
pending_msgs: Rc::downgrade(&this.pending_msgs),
pending_cmds: Rc::downgrade(&this.pending_cmds),
Expand All @@ -114,6 +129,8 @@ where
app: Rc::clone(&self.app),
#[cfg(feature = "use-template")]
template: self.template.clone(),
#[cfg(feature = "use-template")]
vdom_template: self.vdom_template.clone(),
current_vdom: Rc::clone(&self.current_vdom),
pending_msgs: Rc::clone(&self.pending_msgs),
pending_cmds: Rc::clone(&self.pending_cmds),
Expand All @@ -126,17 +143,6 @@ where
MSG: 'static,
APP: Application<MSG>,
{
/// Creates a new app, the view is created
pub fn new(app: APP, _template: web_sys::Node, vdom_template: vdom::Node<MSG>) -> Self {
Self {
app: Rc::new(RefCell::new(app)),
#[cfg(feature = "use-template")]
template: _template,
current_vdom: Rc::new(RefCell::new(vdom_template)),
pending_msgs: Rc::new(RefCell::new(VecDeque::new())),
pending_cmds: Rc::new(RefCell::new(VecDeque::new())),
}
}

pub fn init_app(&self) -> Cmd<APP, MSG> {
self.app.borrow_mut().init()
Expand Down
6 changes: 3 additions & 3 deletions examples/experimentals/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl Application<Msg> for App {
window()
.set_interval_with_callback_and_timeout_and_arguments_0(
clock.as_ref().unchecked_ref(),
1000,
5000,
)
.expect("Unable to start interval");
program2.closures.borrow_mut().push(clock);
Expand Down Expand Up @@ -175,10 +175,10 @@ impl Application<Msg> for App {
{Component::view(&self.btn).map_msg(Msg::BtnMsg)}
</div>
<div>
{stateful_component(Button::default(), [], [text("External child of btn stateful_component")])}
//{stateful_component(Button::default(), [], [text("External child of btn stateful_component")])}
</div>
<div>
{stateful_component(DateTimeWidget::default(), [],[text("External child of date widget")])}
//{stateful_component(DateTimeWidget::default(), [],[text("External child of date widget")])}
</div>
</div>
}
Expand Down

0 comments on commit 632a287

Please sign in to comment.