tachys/html/attribute/
value.rs

1use crate::renderer::Rndr;
2use std::{
3    borrow::Cow,
4    future::Future,
5    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
6    num::{
7        NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8,
8        NonZeroIsize, NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64,
9        NonZeroU8, NonZeroUsize,
10    },
11    sync::Arc,
12};
13
14/// Declares that this type can be converted into some other type, which is a valid attribute value.
15pub trait IntoAttributeValue {
16    /// The attribute value into which this type can be converted.
17    type Output;
18
19    /// Consumes this value, transforming it into an attribute value.
20    fn into_attribute_value(self) -> Self::Output;
21}
22
23impl<T> IntoAttributeValue for T
24where
25    T: AttributeValue,
26{
27    type Output = Self;
28
29    fn into_attribute_value(self) -> Self::Output {
30        self
31    }
32}
33
34/// A possible value for an HTML attribute.
35pub trait AttributeValue: Send {
36    /// The state that should be retained between building and rebuilding.
37    type State;
38
39    /// The type once all async data have loaded.
40    type AsyncOutput: AttributeValue;
41
42    /// A version of the value that can be cloned. This can be the same type, or a
43    /// reference-counted type. Generally speaking, this does *not* need to refer to the same data,
44    /// but should behave in the same way. So for example, making an event handler cloneable should
45    /// probably make it reference-counted (so that a `FnMut()` continues mutating the same
46    /// closure), but making a `String` cloneable does not necessarily need to make it an
47    /// `Arc<str>`, as two different clones of a `String` will still have the same value.
48    type Cloneable: AttributeValue + Clone;
49
50    /// A cloneable type that is also `'static`. This is used for spreading across types when the
51    /// spreadable attribute needs to be owned. In some cases (`&'a str` to `Arc<str>`, etc.) the owned
52    /// cloneable type has worse performance than the cloneable type, so they are separate.
53    type CloneableOwned: AttributeValue + Clone + 'static;
54
55    /// An approximation of the actual length of this attribute in HTML.
56    fn html_len(&self) -> usize;
57
58    /// Renders the attribute value to HTML.
59    fn to_html(self, key: &str, buf: &mut String);
60
61    /// Renders the attribute value to HTML for a `<template>`.
62    fn to_template(key: &str, buf: &mut String);
63
64    /// Adds interactivity as necessary, given DOM nodes that were created from HTML that has
65    /// either been rendered on the server, or cloned for a `<template>`.
66    fn hydrate<const FROM_SERVER: bool>(
67        self,
68        key: &str,
69        el: &crate::renderer::types::Element,
70    ) -> Self::State;
71
72    /// Adds this attribute to the element during client-side rendering.
73    fn build(
74        self,
75        el: &crate::renderer::types::Element,
76        key: &str,
77    ) -> Self::State;
78
79    /// Applies a new value for the attribute.
80    fn rebuild(self, key: &str, state: &mut Self::State);
81
82    /// Converts this attribute into an equivalent that can be cloned.
83    fn into_cloneable(self) -> Self::Cloneable;
84
85    /// Converts this attributes into an equivalent that can be cloned and is `'static`.
86    fn into_cloneable_owned(self) -> Self::CloneableOwned;
87
88    /// “Runs” the attribute without other side effects. For primitive types, this is a no-op. For
89    /// reactive types, this can be used to gather data about reactivity or about asynchronous data
90    /// that needs to be loaded.
91    fn dry_resolve(&mut self);
92
93    /// “Resolves” this into a form that is not waiting for any asynchronous data.
94    fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send;
95}
96
97impl AttributeValue for () {
98    type State = ();
99    type AsyncOutput = ();
100    type Cloneable = ();
101    type CloneableOwned = ();
102
103    fn html_len(&self) -> usize {
104        0
105    }
106
107    fn to_html(self, _key: &str, _buf: &mut String) {}
108
109    fn to_template(_key: &str, _buf: &mut String) {}
110
111    fn hydrate<const FROM_SERVER: bool>(
112        self,
113        _key: &str,
114        _el: &crate::renderer::types::Element,
115    ) {
116    }
117
118    fn build(
119        self,
120        _el: &crate::renderer::types::Element,
121        _key: &str,
122    ) -> Self::State {
123    }
124
125    fn rebuild(self, _key: &str, _state: &mut Self::State) {}
126
127    fn into_cloneable(self) -> Self::Cloneable {
128        self
129    }
130
131    fn into_cloneable_owned(self) -> Self::CloneableOwned {
132        self
133    }
134
135    fn dry_resolve(&mut self) {}
136
137    async fn resolve(self) {}
138}
139
140impl<'a> AttributeValue for &'a str {
141    type State = (crate::renderer::types::Element, &'a str);
142    type AsyncOutput = &'a str;
143    type Cloneable = &'a str;
144    type CloneableOwned = Arc<str>;
145
146    fn html_len(&self) -> usize {
147        self.len()
148    }
149
150    fn to_html(self, key: &str, buf: &mut String) {
151        buf.push(' ');
152        buf.push_str(key);
153        buf.push_str("=\"");
154        buf.push_str(&escape_attr(self));
155        buf.push('"');
156    }
157
158    fn to_template(_key: &str, _buf: &mut String) {}
159
160    fn hydrate<const FROM_SERVER: bool>(
161        self,
162        key: &str,
163        el: &crate::renderer::types::Element,
164    ) -> Self::State {
165        // if we're actually hydrating from SSRed HTML, we don't need to set the attribute
166        // if we're hydrating from a CSR-cloned <template>, we do need to set non-StaticAttr attributes
167        if !FROM_SERVER {
168            Rndr::set_attribute(el, key, self);
169        }
170        (el.clone(), self)
171    }
172
173    fn build(
174        self,
175        el: &crate::renderer::types::Element,
176        key: &str,
177    ) -> Self::State {
178        Rndr::set_attribute(el, key, self);
179        (el.to_owned(), self)
180    }
181
182    fn rebuild(self, key: &str, state: &mut Self::State) {
183        let (el, prev_value) = state;
184        if self != *prev_value {
185            Rndr::set_attribute(el, key, self);
186        }
187        *prev_value = self;
188    }
189
190    fn into_cloneable(self) -> Self::Cloneable {
191        self
192    }
193
194    fn into_cloneable_owned(self) -> Self::CloneableOwned {
195        self.into()
196    }
197
198    fn dry_resolve(&mut self) {}
199
200    async fn resolve(self) -> Self::AsyncOutput {
201        self
202    }
203}
204
205impl<'a> AttributeValue for Cow<'a, str> {
206    type State = (crate::renderer::types::Element, Self);
207    type AsyncOutput = Self;
208    type Cloneable = Arc<str>;
209    type CloneableOwned = Arc<str>;
210
211    fn html_len(&self) -> usize {
212        self.len()
213    }
214
215    fn to_html(self, key: &str, buf: &mut String) {
216        buf.push(' ');
217        buf.push_str(key);
218        buf.push_str("=\"");
219        buf.push_str(&escape_attr(&self));
220        buf.push('"');
221    }
222
223    fn to_template(_key: &str, _buf: &mut String) {}
224
225    fn hydrate<const FROM_SERVER: bool>(
226        self,
227        key: &str,
228        el: &crate::renderer::types::Element,
229    ) -> Self::State {
230        // if we're actually hydrating from SSRed HTML, we don't need to set the attribute
231        // if we're hydrating from a CSR-cloned <template>, we do need to set non-StaticAttr attributes
232        if !FROM_SERVER {
233            Rndr::set_attribute(el, key, &self);
234        }
235        (el.clone(), self)
236    }
237
238    fn build(
239        self,
240        el: &crate::renderer::types::Element,
241        key: &str,
242    ) -> Self::State {
243        Rndr::set_attribute(el, key, &self);
244        (el.to_owned(), self)
245    }
246
247    fn rebuild(self, key: &str, state: &mut Self::State) {
248        let (el, prev_value) = state;
249        if self != *prev_value {
250            Rndr::set_attribute(el, key, &self);
251        }
252        *prev_value = self;
253    }
254
255    fn into_cloneable(self) -> Self::Cloneable {
256        self.into()
257    }
258
259    fn into_cloneable_owned(self) -> Self::CloneableOwned {
260        self.into()
261    }
262
263    fn dry_resolve(&mut self) {}
264
265    async fn resolve(self) -> Self::AsyncOutput {
266        self
267    }
268}
269
270#[cfg(all(feature = "nightly", rustc_nightly))]
271impl<const V: &'static str> AttributeValue
272    for crate::view::static_types::Static<V>
273{
274    type AsyncOutput = Self;
275    type State = ();
276    type Cloneable = Self;
277    type CloneableOwned = Self;
278
279    fn html_len(&self) -> usize {
280        V.len()
281    }
282
283    fn to_html(self, key: &str, buf: &mut String) {
284        <&str as AttributeValue>::to_html(V, key, buf);
285    }
286
287    fn to_template(key: &str, buf: &mut String) {
288        buf.push(' ');
289        buf.push_str(key);
290        buf.push_str("=\"");
291        buf.push_str(V);
292        buf.push('"');
293    }
294
295    fn hydrate<const FROM_SERVER: bool>(
296        self,
297        _key: &str,
298        _el: &crate::renderer::types::Element,
299    ) -> Self::State {
300    }
301
302    fn build(
303        self,
304        el: &crate::renderer::types::Element,
305        key: &str,
306    ) -> Self::State {
307        <&str as AttributeValue>::build(V, el, key);
308    }
309
310    fn rebuild(self, _key: &str, _state: &mut Self::State) {}
311
312    fn into_cloneable(self) -> Self::Cloneable {
313        self
314    }
315
316    fn into_cloneable_owned(self) -> Self::CloneableOwned {
317        self
318    }
319
320    fn dry_resolve(&mut self) {}
321
322    async fn resolve(self) -> Self::AsyncOutput {
323        self
324    }
325}
326
327impl<'a> AttributeValue for &'a String {
328    type AsyncOutput = Self;
329    type State = (crate::renderer::types::Element, &'a String);
330    type Cloneable = Self;
331    type CloneableOwned = Arc<str>;
332
333    fn html_len(&self) -> usize {
334        self.len()
335    }
336
337    fn to_html(self, key: &str, buf: &mut String) {
338        <&str as AttributeValue>::to_html(self.as_str(), key, buf);
339    }
340
341    fn to_template(_key: &str, _buf: &mut String) {}
342
343    fn hydrate<const FROM_SERVER: bool>(
344        self,
345        key: &str,
346        el: &crate::renderer::types::Element,
347    ) -> Self::State {
348        let (el, _) = <&str as AttributeValue>::hydrate::<FROM_SERVER>(
349            self.as_str(),
350            key,
351            el,
352        );
353        (el, self)
354    }
355
356    fn build(
357        self,
358        el: &crate::renderer::types::Element,
359        key: &str,
360    ) -> Self::State {
361        Rndr::set_attribute(el, key, self);
362        (el.clone(), self)
363    }
364
365    fn rebuild(self, key: &str, state: &mut Self::State) {
366        let (el, prev_value) = state;
367        if self != *prev_value {
368            Rndr::set_attribute(el, key, self);
369        }
370        *prev_value = self;
371    }
372
373    fn into_cloneable(self) -> Self::Cloneable {
374        self
375    }
376
377    fn into_cloneable_owned(self) -> Self::CloneableOwned {
378        self.as_str().into()
379    }
380
381    fn dry_resolve(&mut self) {}
382
383    async fn resolve(self) -> Self::AsyncOutput {
384        self
385    }
386}
387
388impl AttributeValue for String {
389    type AsyncOutput = Self;
390    type State = (crate::renderer::types::Element, String);
391    type Cloneable = Arc<str>;
392    type CloneableOwned = Arc<str>;
393
394    fn html_len(&self) -> usize {
395        self.len()
396    }
397
398    fn to_html(self, key: &str, buf: &mut String) {
399        <&str as AttributeValue>::to_html(self.as_str(), key, buf);
400    }
401
402    fn to_template(_key: &str, _buf: &mut String) {}
403
404    fn hydrate<const FROM_SERVER: bool>(
405        self,
406        key: &str,
407        el: &crate::renderer::types::Element,
408    ) -> Self::State {
409        let (el, _) = <&str as AttributeValue>::hydrate::<FROM_SERVER>(
410            self.as_str(),
411            key,
412            el,
413        );
414        (el, self)
415    }
416
417    fn build(
418        self,
419        el: &crate::renderer::types::Element,
420        key: &str,
421    ) -> Self::State {
422        Rndr::set_attribute(el, key, &self);
423        (el.clone(), self)
424    }
425
426    fn rebuild(self, key: &str, state: &mut Self::State) {
427        let (el, prev_value) = state;
428        if self != *prev_value {
429            Rndr::set_attribute(el, key, &self);
430        }
431        *prev_value = self;
432    }
433
434    fn into_cloneable(self) -> Self::Cloneable {
435        self.into()
436    }
437
438    fn into_cloneable_owned(self) -> Self::CloneableOwned {
439        self.into()
440    }
441
442    fn dry_resolve(&mut self) {}
443
444    async fn resolve(self) -> Self::AsyncOutput {
445        self
446    }
447}
448
449impl AttributeValue for Arc<str> {
450    type AsyncOutput = Self;
451    type State = (crate::renderer::types::Element, Arc<str>);
452    type Cloneable = Arc<str>;
453    type CloneableOwned = Arc<str>;
454
455    fn html_len(&self) -> usize {
456        self.len()
457    }
458
459    fn to_html(self, key: &str, buf: &mut String) {
460        <&str as AttributeValue>::to_html(self.as_ref(), key, buf);
461    }
462
463    fn to_template(_key: &str, _buf: &mut String) {}
464
465    fn hydrate<const FROM_SERVER: bool>(
466        self,
467        key: &str,
468        el: &crate::renderer::types::Element,
469    ) -> Self::State {
470        let (el, _) = <&str as AttributeValue>::hydrate::<FROM_SERVER>(
471            self.as_ref(),
472            key,
473            el,
474        );
475        (el, self)
476    }
477
478    fn build(
479        self,
480        el: &crate::renderer::types::Element,
481        key: &str,
482    ) -> Self::State {
483        Rndr::set_attribute(el, key, &self);
484        (el.clone(), self)
485    }
486
487    fn rebuild(self, key: &str, state: &mut Self::State) {
488        let (el, prev_value) = state;
489        if self != *prev_value {
490            Rndr::set_attribute(el, key, &self);
491        }
492        *prev_value = self;
493    }
494
495    fn into_cloneable(self) -> Self::Cloneable {
496        self
497    }
498
499    fn into_cloneable_owned(self) -> Self::CloneableOwned {
500        self
501    }
502
503    fn dry_resolve(&mut self) {}
504
505    async fn resolve(self) -> Self::AsyncOutput {
506        self
507    }
508}
509// TODO impl AttributeValue for Rc<str> and Arc<str> too
510
511impl AttributeValue for bool {
512    type AsyncOutput = Self;
513    type State = (crate::renderer::types::Element, bool);
514    type Cloneable = Self;
515    type CloneableOwned = Self;
516
517    fn html_len(&self) -> usize {
518        0
519    }
520
521    fn to_html(self, key: &str, buf: &mut String) {
522        if self {
523            buf.push(' ');
524            buf.push_str(key);
525        }
526    }
527
528    fn to_template(_key: &str, _buf: &mut String) {}
529
530    fn hydrate<const FROM_SERVER: bool>(
531        self,
532        key: &str,
533        el: &crate::renderer::types::Element,
534    ) -> Self::State {
535        // if we're actually hydrating from SSRed HTML, we don't need to set the attribute
536        // if we're hydrating from a CSR-cloned <template>, we do need to set non-StaticAttr attributes
537        if !FROM_SERVER {
538            Rndr::set_attribute(el, key, "");
539        }
540        (el.clone(), self)
541    }
542
543    fn build(
544        self,
545        el: &crate::renderer::types::Element,
546        key: &str,
547    ) -> Self::State {
548        if self {
549            Rndr::set_attribute(el, key, "");
550        }
551        (el.clone(), self)
552    }
553
554    fn rebuild(self, key: &str, state: &mut Self::State) {
555        let (el, prev_value) = state;
556        if self != *prev_value {
557            if self {
558                Rndr::set_attribute(el, key, "");
559            } else {
560                Rndr::remove_attribute(el, key);
561            }
562        }
563        *prev_value = self;
564    }
565
566    fn into_cloneable(self) -> Self::Cloneable {
567        self
568    }
569
570    fn into_cloneable_owned(self) -> Self::CloneableOwned {
571        self
572    }
573
574    fn dry_resolve(&mut self) {}
575
576    async fn resolve(self) -> Self::AsyncOutput {
577        self
578    }
579}
580
581impl<V> AttributeValue for Option<V>
582where
583    V: AttributeValue,
584{
585    type AsyncOutput = Option<V::AsyncOutput>;
586    type State = (crate::renderer::types::Element, Option<V::State>);
587    type Cloneable = Option<V::Cloneable>;
588    type CloneableOwned = Option<V::CloneableOwned>;
589
590    fn html_len(&self) -> usize {
591        match self {
592            Some(i) => i.html_len(),
593            None => 0,
594        }
595    }
596
597    fn to_html(self, key: &str, buf: &mut String) {
598        if let Some(v) = self {
599            v.to_html(key, buf);
600        }
601    }
602
603    fn to_template(_key: &str, _buf: &mut String) {}
604
605    fn hydrate<const FROM_SERVER: bool>(
606        self,
607        key: &str,
608        el: &crate::renderer::types::Element,
609    ) -> Self::State {
610        let state = self.map(|v| v.hydrate::<FROM_SERVER>(key, el));
611        (el.clone(), state)
612    }
613
614    fn build(
615        self,
616        el: &crate::renderer::types::Element,
617        key: &str,
618    ) -> Self::State {
619        let el = el.clone();
620        let v = self.map(|v| v.build(&el, key));
621        (el, v)
622    }
623
624    fn rebuild(self, key: &str, state: &mut Self::State) {
625        let (el, prev) = state;
626        match (self, prev.as_mut()) {
627            (None, None) => {}
628            (None, Some(_)) => {
629                Rndr::remove_attribute(el, key);
630                *prev = None;
631            }
632            (Some(value), None) => {
633                *prev = Some(value.build(el, key));
634            }
635            (Some(new), Some(old)) => {
636                new.rebuild(key, old);
637            }
638        }
639    }
640
641    fn into_cloneable(self) -> Self::Cloneable {
642        self.map(|value| value.into_cloneable())
643    }
644
645    fn into_cloneable_owned(self) -> Self::CloneableOwned {
646        self.map(|value| value.into_cloneable_owned())
647    }
648
649    fn dry_resolve(&mut self) {
650        if let Some(inner) = self.as_mut() {
651            inner.dry_resolve();
652        }
653    }
654
655    async fn resolve(self) -> Self::AsyncOutput {
656        match self {
657            None => None,
658            Some(inner) => Some(inner.resolve().await),
659        }
660    }
661}
662
663pub(crate) fn escape_attr(value: &str) -> Cow<'_, str> {
664    html_escape::encode_double_quoted_attribute(value)
665}
666
667macro_rules! render_primitive {
668  ($($child_type:ty),* $(,)?) => {
669      $(
670        impl AttributeValue for $child_type
671        where
672
673        {
674            type AsyncOutput = $child_type;
675            type State = (crate::renderer::types::Element, $child_type);
676            type Cloneable = Self;
677            type CloneableOwned = Self;
678
679            fn html_len(&self) -> usize {
680                0
681            }
682
683            fn to_html(self, key: &str, buf: &mut String) {
684                <String as AttributeValue>::to_html(self.to_string(), key, buf);
685            }
686
687            fn to_template(_key: &str, _buf: &mut String) {}
688
689            fn hydrate<const FROM_SERVER: bool>(
690                self,
691                key: &str,
692                el: &crate::renderer::types::Element,
693            ) -> Self::State {
694                // if we're actually hydrating from SSRed HTML, we don't need to set the attribute
695                // if we're hydrating from a CSR-cloned <template>, we do need to set non-StaticAttr attributes
696                if !FROM_SERVER {
697                    Rndr::set_attribute(el, key, &self.to_string());
698                }
699                (el.clone(), self)
700            }
701
702            fn build(self, el: &crate::renderer::types::Element, key: &str) -> Self::State {
703                Rndr::set_attribute(el, key, &self.to_string());
704                (el.to_owned(), self)
705            }
706
707            fn rebuild(self, key: &str, state: &mut Self::State) {
708                let (el, prev_value) = state;
709                if self != *prev_value {
710                    Rndr::set_attribute(el, key, &self.to_string());
711                }
712                *prev_value = self;
713            }
714
715            fn into_cloneable(self) -> Self::Cloneable {
716                self
717            }
718
719            fn into_cloneable_owned(self) -> Self::CloneableOwned {
720                self
721            }
722
723            fn dry_resolve(&mut self) {
724            }
725
726            async fn resolve(self) -> Self::AsyncOutput {
727                self
728            }
729        }
730      )*
731  }
732}
733
734render_primitive![
735    usize,
736    u8,
737    u16,
738    u32,
739    u64,
740    u128,
741    isize,
742    i8,
743    i16,
744    i32,
745    i64,
746    i128,
747    f32,
748    f64,
749    char,
750    IpAddr,
751    SocketAddr,
752    SocketAddrV4,
753    SocketAddrV6,
754    Ipv4Addr,
755    Ipv6Addr,
756    NonZeroI8,
757    NonZeroU8,
758    NonZeroI16,
759    NonZeroU16,
760    NonZeroI32,
761    NonZeroU32,
762    NonZeroI64,
763    NonZeroU64,
764    NonZeroI128,
765    NonZeroU128,
766    NonZeroIsize,
767    NonZeroUsize,
768];