sauron_core/vdom/
attribute.rs

1#![allow(clippy::type_complexity)]
2
3use crate::vdom::ComponentEventCallback;
4use crate::vdom::EventCallback;
5use derive_where::derive_where;
6use indexmap::IndexMap;
7
8pub use attribute_value::AttributeValue;
9pub use callback::Callback;
10pub use style::Style;
11pub use value::Value;
12
13mod attribute_value;
14pub mod callback;
15pub(crate) mod special;
16mod style;
17mod value;
18
19/// The type of the Namspace
20pub type Namespace = &'static str;
21
22/// The type of the Tag
23pub type Tag = &'static str;
24
25/// The type of Attribute Name
26pub type AttributeName = &'static str;
27
28/// These are the plain attributes of an element
29#[derive_where(Clone, Debug, PartialEq, Eq)]
30pub struct Attribute<MSG> {
31    /// namespace of an attribute.
32    /// This is specifically used by svg attributes
33    /// such as xlink-href
34    pub namespace: Option<Namespace>,
35    /// the attribute name,
36    /// optional since style attribute doesn't need to have an attribute name
37    pub name: AttributeName,
38    /// the attribute value, which could be a simple value, and event or a function call
39    pub value: Vec<AttributeValue<MSG>>,
40}
41
42/// The Attributes partition into 4 different types
43pub struct GroupedAttributeValues<'a, MSG> {
44    /// the event listeners
45    pub listeners: Vec<&'a EventCallback<MSG>>,
46    /// the component event listeners
47    pub component_callbacks: Vec<&'a ComponentEventCallback>,
48    /// plain attribute values
49    pub plain_values: Vec<&'a Value>,
50    /// style attribute values
51    pub styles: Vec<&'a Style>,
52}
53
54impl<MSG> Attribute<MSG> {
55    /// create a plain attribute with namespace
56    pub fn new(
57        namespace: Option<Namespace>,
58        name: AttributeName,
59        value: AttributeValue<MSG>,
60    ) -> Self {
61        Attribute {
62            name,
63            value: vec![value],
64            namespace,
65        }
66    }
67
68    /// create from multiple values
69    pub fn with_multiple_values(
70        namespace: Option<Namespace>,
71        name: AttributeName,
72        value: impl IntoIterator<Item = AttributeValue<MSG>>,
73    ) -> Self {
74        Attribute {
75            name,
76            value: value.into_iter().collect(),
77            namespace,
78        }
79    }
80
81    /// return the name of this attribute
82    pub fn name(&self) -> &AttributeName {
83        &self.name
84    }
85
86    /// return the value of this attribute
87    pub fn value(&self) -> &[AttributeValue<MSG>] {
88        &self.value
89    }
90
91    /// return the namespace of this attribute
92    pub fn namespace(&self) -> Option<&Namespace> {
93        self.namespace.as_ref()
94    }
95
96    /// returns true if this attribute is an event listener
97    pub fn is_event_listener(&self) -> bool {
98        self.value
99            .first()
100            .map(|v| v.is_event_listener())
101            .unwrap_or(false)
102    }
103
104    /// grouped values into plain, function calls, styles and event listeners
105    pub(crate) fn group_values(attr: &Attribute<MSG>) -> GroupedAttributeValues<MSG> {
106        let mut listeners = vec![];
107        let mut component_callbacks = vec![];
108        let mut plain_values = vec![];
109        let mut styles = vec![];
110        for av in attr.value() {
111            match av {
112                AttributeValue::Simple(v) => {
113                    plain_values.push(v);
114                }
115                AttributeValue::Style(v) => {
116                    styles.extend(v);
117                }
118                AttributeValue::EventListener(cb) => {
119                    listeners.push(cb);
120                }
121                AttributeValue::ComponentEventListener(cb) => {
122                    component_callbacks.push(cb);
123                }
124                AttributeValue::Empty => (),
125            }
126        }
127        GroupedAttributeValues {
128            listeners,
129            component_callbacks,
130            plain_values,
131            styles,
132        }
133    }
134
135    fn is_just_empty(&self) -> bool {
136        self.value
137            .first()
138            .map(|av| av.is_just_empty())
139            .unwrap_or(false)
140    }
141
142    pub(crate) fn is_mount_callback(&self) -> bool {
143        self.name == "mount"
144    }
145
146    /// merge the values of attributes with the same name
147    /// also exclude the empty attribute
148    pub fn merge_attributes_of_same_name<'a>(
149        attributes: impl IntoIterator<Item = &'a Attribute<MSG>> + Iterator,
150    ) -> Vec<Attribute<MSG>>
151    where
152        MSG: 'a,
153    {
154        let mut merged: IndexMap<&AttributeName, Attribute<MSG>> =
155            IndexMap::with_capacity(attributes.size_hint().0);
156        for att in attributes.into_iter() {
157            if !att.is_just_empty() {
158                if let Some(existing) = merged.get_mut(&att.name) {
159                    existing.value.extend(att.value.clone());
160                } else {
161                    merged.insert(
162                        &att.name,
163                        Attribute {
164                            namespace: att.namespace,
165                            name: att.name,
166                            value: att.value.clone(),
167                        },
168                    );
169                }
170            }
171        }
172        merged.into_values().collect()
173    }
174
175    /// group attributes of the same name
176    #[doc(hidden)]
177    pub fn group_attributes_per_name<'a>(
178        attributes: impl IntoIterator<Item = &'a Attribute<MSG>> + Iterator,
179    ) -> IndexMap<&'a AttributeName, Vec<&'a Attribute<MSG>>> {
180        let mut grouped: IndexMap<&'a AttributeName, Vec<&'a Attribute<MSG>>> =
181            IndexMap::with_capacity(attributes.size_hint().0);
182        for attr in attributes {
183            if let Some(existing) = grouped.get_mut(&attr.name) {
184                existing.push(attr);
185            } else {
186                grouped.insert(&attr.name, vec![attr]);
187            }
188        }
189        grouped
190    }
191}
192
193/// Create an attribute
194/// # Example
195/// ```rust
196/// use sauron::vdom::{Attribute,attr};
197/// let class: Attribute<()> = attr("class", "container");
198/// ```
199#[inline]
200pub fn attr<MSG>(name: AttributeName, value: impl Into<AttributeValue<MSG>>) -> Attribute<MSG> {
201    attr_ns(None, name, value)
202}
203
204/// Create an attribute with namespace
205/// # Example
206/// ```rust
207/// use sauron::vdom::{Attribute,attr_ns};
208///
209/// let href: Attribute<()> = attr_ns(Some("http://www.w3.org/1999/xlink"), "href", "cool-script.js");
210/// ```
211#[inline]
212pub fn attr_ns<MSG>(
213    namespace: Option<Namespace>,
214    name: AttributeName,
215    value: impl Into<AttributeValue<MSG>>,
216) -> Attribute<MSG> {
217    Attribute::new(namespace, name, value.into())
218}