cabin/html/elements/
common.rs1use std::borrow::Cow;
2use std::fmt;
3use std::ops::{Add, AddAssign};
4
5use cabin_macros::Attribute;
6
7use super::SerializeEventFn;
8use crate::error::InternalError;
9use crate::html::attributes::{Attributes, WithAttribute};
10
11pub trait Common: WithAttribute {
12 fn id(self, id: impl Into<Cow<'static, str>>) -> Self::Output<Id> {
14 self.with_attribute(Id(id.into()))
15 }
16
17 fn class(mut self, class: impl Into<Cow<'static, str>>) -> Self::Output<Class> {
19 let class = if let Some(existing) = self.get_attribute_mut::<Class>() {
20 Class(Cow::Owned(format!("{} {}", existing.0, class.into())))
21 } else {
22 Class(class.into())
23 };
24 self.with_attribute(class)
25 }
26
27 fn replace_class(self, class: impl Into<Cow<'static, str>>) -> Self::Output<Class> {
28 self.with_attribute(Class(class.into()))
29 }
30
31 fn on_click<E>(self, event: E) -> Self::Output<OnClick>
32 where
33 E: serde::Serialize + 'static,
34 {
35 self.with_attribute(OnClick(Box::new(move || {
36 use std::hash::{Hash, Hasher};
37
38 let mut hasher = twox_hash::XxHash32::default();
39 std::any::TypeId::of::<E>().hash(&mut hasher);
40 let hash = hasher.finish() as u32;
41 serde_json::to_string(&event)
42 .map_err(|err| InternalError::Serialize {
43 what: "on_click event",
44 err,
45 })
46 .map(|json| (hash, json))
47 })))
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
53pub struct Id(pub Cow<'static, str>);
54
55#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
58pub struct Class(pub Cow<'static, str>);
59
60pub struct OnClick(pub Box<SerializeEventFn>);
61
62impl Attributes for OnClick {
63 fn render(self, r: &mut crate::render::ElementRenderer) -> Result<(), crate::Error> {
64 let (id, payload) = &(self.0)()?;
66 r.attribute("cabin-click", id)
67 .map_err(crate::error::InternalError::from)?;
68 r.attribute("cabin-click-payload", payload)
69 .map_err(crate::error::InternalError::from)?;
70
71 Ok(())
72 }
73}
74
75impl Class {
76 pub fn append(self, other: Class) -> Class {
77 self + other
78 }
79
80 pub fn append_when(self, condition: bool, other: Class) -> Class {
81 if !condition {
82 self
83 } else {
84 self + other
85 }
86 }
87}
88
89impl Default for Class {
90 fn default() -> Self {
91 Class(Cow::Borrowed(""))
92 }
93}
94
95impl From<Class> for Cow<'static, str> {
96 fn from(value: Class) -> Self {
97 value.0
98 }
99}
100
101impl From<Option<&'static str>> for Class {
102 fn from(value: Option<&'static str>) -> Self {
103 Class(value.map(Cow::Borrowed).unwrap_or(Cow::Borrowed("")))
104 }
105}
106
107impl Add<Class> for Class {
108 type Output = Class;
109
110 fn add(self, rhs: Class) -> Self::Output {
111 Class(Cow::Owned(format!("{self} {rhs}")))
113 }
114}
115
116impl Add<Option<Class>> for Class {
117 type Output = Class;
118
119 fn add(self, rhs: Option<Class>) -> Self::Output {
120 if let Some(rhs) = rhs {
121 Class(Cow::Owned(format!("{self} {rhs}")))
123 } else {
124 self
125 }
126 }
127}
128
129impl Add<Class> for Option<Class> {
130 type Output = Class;
131
132 fn add(self, rhs: Class) -> Self::Output {
133 if let Some(lhs) = self {
134 Class(Cow::Owned(format!("{lhs} {rhs}")))
136 } else {
137 rhs
138 }
139 }
140}
141
142impl AddAssign for Class {
143 fn add_assign(&mut self, rhs: Self) {
144 *self = Class(Cow::Owned(format!("{self} {rhs}")))
146 }
147}
148
149impl fmt::Display for Class {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 self.0.fmt(f)
152 }
153}