Skip to content

Commit

Permalink
xilem_html: Introduce dom-attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
Philipp-M committed Nov 3, 2023
1 parent f7d351f commit 039888a
Show file tree
Hide file tree
Showing 20 changed files with 697 additions and 19 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
members = [
"crates/xilem_core",
"crates/xilem_html",
"crates/xilem_html/web_examples/after_update",
"crates/xilem_html/web_examples/counter",
"crates/xilem_html/web_examples/counter_custom_element",
"crates/xilem_html/web_examples/play_video",
"crates/xilem_html/web_examples/todomvc",
"crates/xilem_svg",
"crates/xilem_html/web_examples/after_update",
]

[workspace.package]
Expand Down
2 changes: 1 addition & 1 deletion crates/xilem_core/src/view/memoize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ macro_rules! generate_memoize_view {

impl<T, A, D, V, F> $viewtrait<T, A> for $memoizeview<D, F>
where
D: PartialEq $( $ss )* + 'static,
D: PartialEq $( $ss )*,
V: $viewtrait<T, A>,
F: Fn(&D) -> V $( $ss )*,
{
Expand Down
148 changes: 148 additions & 0 deletions crates/xilem_html/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ xilem_core.workspace = true
kurbo.workspace = true
bitflags = "2"
wasm-bindgen = "0.2.87"
paste = "1"
log = "0.4.19"
gloo = { version = "0.8.1", default-features = false, features = ["events", "utils"] }

Expand Down Expand Up @@ -116,3 +117,150 @@ features = [
"HtmlUListElement",
"HtmlVideoElement",
]

[features]
HtmlElement = []
HtmlAnchorElement = ["HtmlElement"]
HtmlAreaElement = ["HtmlElement"]
HtmlAudioElement = ["HtmlMediaElement"]
HtmlBaseElement = ["HtmlElement"]
HtmlBodyElement = ["HtmlElement"]
HtmlBrElement = ["HtmlElement"]
HtmlButtonElement = ["HtmlElement"]
HtmlCanvasElement = ["HtmlElement"]
HtmlDataElement = ["HtmlElement"]
HtmlDataListElement = ["HtmlElement"]
HtmlDetailsElement = ["HtmlElement"]
HtmlDialogElement = ["HtmlElement"]
HtmlDirectoryElement = ["HtmlElement"]
HtmlDivElement = ["HtmlElement"]
HtmlDListElement = ["HtmlElement"]
HtmlUnknownElement = ["HtmlElement"]
HtmlEmbedElement = ["HtmlElement"]
HtmlFieldSetElement = ["HtmlElement"]
HtmlFontElement = ["HtmlElement"]
HtmlFormElement = ["HtmlElement"]
HtmlFrameElement = ["HtmlElement"]
HtmlFrameSetElement = ["HtmlElement"]
HtmlHeadElement = ["HtmlElement"]
HtmlHeadingElement = ["HtmlElement"]
HtmlHrElement = ["HtmlElement"]
HtmlHtmlElement = ["HtmlElement"]
HtmlIFrameElement = ["HtmlElement"]
HtmlImageElement = ["HtmlElement"]
HtmlInputElement = ["HtmlElement"]
HtmlLabelElement = ["HtmlElement"]
HtmlLegendElement = ["HtmlElement"]
HtmlLiElement = ["HtmlElement"]
HtmlLinkElement = ["HtmlElement"]
HtmlMapElement = ["HtmlElement"]
HtmlMediaElement = ["HtmlElement"]
HtmlMenuElement = ["HtmlElement"]
HtmlMenuItemElement = ["HtmlElement"]
HtmlMetaElement = ["HtmlElement"]
HtmlMeterElement = ["HtmlElement"]
HtmlModElement = ["HtmlElement"]
HtmlObjectElement = ["HtmlElement"]
HtmlOListElement = ["HtmlElement"]
HtmlOptGroupElement = ["HtmlElement"]
HtmlOptionElement = ["HtmlElement"]
HtmlOutputElement = ["HtmlElement"]
HtmlParagraphElement = ["HtmlElement"]
HtmlParamElement = ["HtmlElement"]
HtmlPictureElement = ["HtmlElement"]
HtmlPreElement = ["HtmlElement"]
HtmlProgressElement = ["HtmlElement"]
HtmlQuoteElement = ["HtmlElement"]
HtmlScriptElement = ["HtmlElement"]
HtmlSelectElement = ["HtmlElement"]
HtmlSlotElement = ["HtmlElement"]
HtmlSourceElement = ["HtmlElement"]
HtmlSpanElement = ["HtmlElement"]
HtmlStyleElement = ["HtmlElement"]
HtmlTableCaptionElement = ["HtmlElement"]
HtmlTableCellElement = ["HtmlElement"]
HtmlTableColElement = ["HtmlElement"]
HtmlTableElement = ["HtmlElement"]
HtmlTableRowElement = ["HtmlElement"]
HtmlTableSectionElement = ["HtmlElement"]
HtmlTemplateElement = ["HtmlElement"]
HtmlTimeElement = ["HtmlElement"]
HtmlTextAreaElement = ["HtmlElement"]
HtmlTitleElement = ["HtmlElement"]
HtmlTrackElement = ["HtmlElement"]
HtmlUListElement = ["HtmlElement"]
HtmlVideoElement = ["HtmlMediaElement"]

typed = [
"HtmlElement",
"HtmlAnchorElement",
"HtmlAreaElement",
"HtmlAudioElement",
"HtmlBaseElement",
"HtmlBodyElement",
"HtmlBrElement",
"HtmlButtonElement",
"HtmlCanvasElement",
"HtmlDataElement",
"HtmlDataListElement",
"HtmlDetailsElement",
"HtmlDialogElement",
"HtmlDirectoryElement",
"HtmlDivElement",
"HtmlDListElement",
"HtmlUnknownElement",
"HtmlEmbedElement",
"HtmlFieldSetElement",
"HtmlFontElement",
"HtmlFormElement",
"HtmlFrameElement",
"HtmlFrameSetElement",
"HtmlHeadElement",
"HtmlHeadingElement",
"HtmlHrElement",
"HtmlHtmlElement",
"HtmlIFrameElement",
"HtmlImageElement",
"HtmlInputElement",
"HtmlLabelElement",
"HtmlLegendElement",
"HtmlLiElement",
"HtmlLinkElement",
"HtmlMapElement",
"HtmlMediaElement",
"HtmlMenuElement",
"HtmlMenuItemElement",
"HtmlMetaElement",
"HtmlMeterElement",
"HtmlModElement",
"HtmlObjectElement",
"HtmlOListElement",
"HtmlOptGroupElement",
"HtmlOptionElement",
"HtmlOutputElement",
"HtmlParagraphElement",
"HtmlParamElement",
"HtmlPictureElement",
"HtmlPreElement",
"HtmlProgressElement",
"HtmlQuoteElement",
"HtmlScriptElement",
"HtmlSelectElement",
"HtmlSlotElement",
"HtmlSourceElement",
"HtmlSpanElement",
"HtmlStyleElement",
"HtmlTableCaptionElement",
"HtmlTableCellElement",
"HtmlTableColElement",
"HtmlTableElement",
"HtmlTableRowElement",
"HtmlTableSectionElement",
"HtmlTemplateElement",
"HtmlTimeElement",
"HtmlTextAreaElement",
"HtmlTitleElement",
"HtmlTrackElement",
"HtmlUListElement",
"HtmlVideoElement",
]
56 changes: 56 additions & 0 deletions crates/xilem_html/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use xilem_core::{Id, IdPath};
use crate::{
app::AppRunner,
diff::{diff_kv_iterables, Diff},
dom_attributes::DomAttr,
vecmap::VecMap,
AttributeValue, Message, HTML_NS, SVG_NS,
};
Expand Down Expand Up @@ -47,6 +48,7 @@ pub struct Cx {
document: Document,
// TODO There's likely a cleaner more robust way to propagate the attributes to an element
pub(crate) current_element_attributes: VecMap<CowStr, AttributeValue>,
pub(crate) current_element_dom_attributes: Vec<DomAttr>,
/// The first tuple element indicates that the after update is still in use, and shouldn't be garbage collected
/// This can happen, if an element that uses after_update is removed, it will be set to true after each rebuild of the `AfterUpdate` View
pub(crate) after_update: BTreeMap<Id, (bool, Vec<Id>)>,
Expand All @@ -73,6 +75,7 @@ impl Cx {
document: crate::document(),
app_ref: None,
current_element_attributes: Default::default(),
current_element_dom_attributes: Vec::new(),
after_update: BTreeMap::new(),
}
}
Expand Down Expand Up @@ -145,6 +148,18 @@ impl Cx {
}
}

#[allow(unused)]
pub(crate) fn add_new_dom_attribute_to_current_element(
&mut self,
// TODO function pointer or static impl Fn()?
is_defined: fn(&DomAttr) -> bool,
value: &DomAttr,
) {
if !self.current_element_dom_attributes.iter().any(is_defined) {
self.current_element_dom_attributes.push(value.clone());
}
}

pub(crate) fn apply_attributes(
&mut self,
element: &web_sys::Element,
Expand All @@ -157,6 +172,47 @@ impl Cx {
attributes
}

#[allow(unused)]
pub(crate) fn apply_dom_attributes(
&mut self,
element: &web_sys::Element,
// TODO function pointer or static impl Fn()?
setter: fn(&web_sys::Element, &DomAttr),
) -> Vec<DomAttr> {
let mut attributes = Vec::new();
std::mem::swap(&mut attributes, &mut self.current_element_dom_attributes);
for attr in attributes.iter() {
setter(element, attr);
}
attributes
}

#[allow(unused)]
pub(crate) fn apply_dom_attribute_changes(
&mut self,
element: &web_sys::Element,
attributes: &mut Vec<DomAttr>,
// TODO function pointer or static impl Fn()?
apply_change: fn(&web_sys::Element, &DomAttr, &DomAttr) -> ChangeFlags,
) -> ChangeFlags {
let mut changed = ChangeFlags::empty();
// TODO not sure if it makes sense to just leave removed DOM attributes at their previous value,
// but this would introduce "side-effects" and may result in unwanted behavior
assert!(
attributes.len() == self.current_element_dom_attributes.len(),
"Currently it's required that there's no changes in the amount of DOM attributes"
);
for (old, new) in attributes
.iter()
.zip(self.current_element_dom_attributes.iter())
{
changed |= apply_change(element, old, new);
}
std::mem::swap(attributes, &mut self.current_element_dom_attributes);
self.current_element_dom_attributes.clear();
changed
}

pub(crate) fn apply_attribute_changes(
&mut self,
element: &web_sys::Element,
Expand Down
49 changes: 49 additions & 0 deletions crates/xilem_html/src/dom_attributes/element.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use super::create_dom_attribute_view;
use crate::{interfaces::for_all_dom_interfaces, ChangeFlags};
use std::borrow::Cow;

#[derive(PartialEq, Clone, Debug, PartialOrd)]
pub enum ElementAttr {
Class(Cow<'static, str>),
}

// TODO currently something like el.class("class1").class("class2") will result in "class2" (i.e. overwrite previous uses of class()) which is maybe not what we want.
// There should probably be a way to add/remove classes when composing the element.
create_dom_attribute_view!(class, Cow<'static, str>, Element);

macro_rules! impl_dom_interface_for_element_dom_attributes {
($dom_interface:ident) => {
impl<T, A, E: $crate::interfaces::$dom_interface<T, A>>
$crate::interfaces::$dom_interface<T, A> for ElementClass<E>
{
}
};
}

// necessary to be different?
for_all_dom_interfaces!(impl_dom_interface_for_element_dom_attributes);

pub(crate) fn build_dom_attribute(el: &web_sys::Element, attr: &ElementAttr) {
match attr {
ElementAttr::Class(class) => {
// benches show, that className is the fastest way to set the class: (https://www.measurethat.net/Benchmarks/Show/5918/0/classname-vs-setattribute-vs-classlist)
el.set_class_name(class);
}
}
}

pub(crate) fn rebuild_dom_attribute(
el: &web_sys::Element,
old: &ElementAttr,
new: &ElementAttr,
) -> ChangeFlags {
match (old, new) {
(ElementAttr::Class(old_class), ElementAttr::Class(new_class))
if old_class != new_class =>
{
el.set_class_name(new_class);
ChangeFlags::OTHER_CHANGE
}
_ => ChangeFlags::empty(),
}
}
42 changes: 42 additions & 0 deletions crates/xilem_html/src/dom_attributes/html_canvas_element.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use super::create_dom_attribute_view;
use crate::ChangeFlags;

#[derive(PartialEq, Clone, Debug, PartialOrd)]
pub enum HtmlCanvasElementAttr {
Width(u32),
Height(u32),
}

create_dom_attribute_view!(width, u32, HtmlCanvasElement);
create_dom_attribute_view!(height, u32, HtmlCanvasElement);

// TODO there may be less boilerplate heavy (but still flexible and non-mentally challenging/complex ways to express the stuff below...)

pub(crate) fn build_dom_attribute(el: &web_sys::HtmlCanvasElement, attr: &HtmlCanvasElementAttr) {
match attr {
HtmlCanvasElementAttr::Width(width) => el.set_width(*width),
HtmlCanvasElementAttr::Height(height) => el.set_height(*height),
}
}

pub(crate) fn rebuild_dom_attribute(
el: &web_sys::HtmlCanvasElement,
old: &HtmlCanvasElementAttr,
new: &HtmlCanvasElementAttr,
) -> ChangeFlags {
match (old, new) {
(HtmlCanvasElementAttr::Width(old_width), HtmlCanvasElementAttr::Width(new_width))
if old_width != new_width =>
{
el.set_width(*new_width);
ChangeFlags::OTHER_CHANGE
}
(HtmlCanvasElementAttr::Height(old_height), HtmlCanvasElementAttr::Height(new_height))
if old_height != new_height =>
{
el.set_height(*new_height);
ChangeFlags::OTHER_CHANGE
}
_ => ChangeFlags::empty(),
}
}
Loading

0 comments on commit 039888a

Please sign in to comment.