Skip to main content

tachys/html/element/
mod.rs

1#[cfg(any(debug_assertions, leptos_debuginfo))]
2use crate::hydration::set_currently_hydrating;
3#[cfg(erase_components)]
4use crate::view::any_view::AnyView;
5use crate::{
6    html::attribute::Attribute,
7    hydration::{failed_to_cast_element, Cursor},
8    renderer::{CastFrom, Rndr},
9    ssr::StreamBuilder,
10    view::{
11        add_attr::AddAnyAttr, IntoRender, Mountable, Position, PositionState,
12        Render, RenderHtml, ToTemplate,
13    },
14};
15use const_str_slice_concat::{
16    const_concat, const_concat_with_prefix, str_from_buffer,
17};
18use futures::future::join;
19use std::ops::Deref;
20
21mod custom;
22mod element_ext;
23mod elements;
24mod inner_html;
25use super::attribute::{
26    any_attribute::AnyAttribute, escape_attr, NextAttribute,
27};
28pub use custom::*;
29pub use element_ext::*;
30pub use elements::*;
31pub use inner_html::*;
32#[cfg(any(debug_assertions, leptos_debuginfo))]
33use std::panic::Location;
34
35/// The typed representation of an HTML element.
36#[derive(Debug, PartialEq, Eq)]
37pub struct HtmlElement<E, At, Ch> {
38    #[cfg(any(debug_assertions, leptos_debuginfo))]
39    pub(crate) defined_at: &'static Location<'static>,
40    pub(crate) tag: E,
41    pub(crate) attributes: At,
42    pub(crate) children: Ch,
43}
44
45impl<E: Clone, At: Clone, Ch: Clone> Clone for HtmlElement<E, At, Ch> {
46    fn clone(&self) -> Self {
47        HtmlElement {
48            #[cfg(any(debug_assertions, leptos_debuginfo))]
49            defined_at: self.defined_at,
50            tag: self.tag.clone(),
51            attributes: self.attributes.clone(),
52            children: self.children.clone(),
53        }
54    }
55}
56
57impl<E: Copy, At: Copy, Ch: Copy> Copy for HtmlElement<E, At, Ch> {}
58
59/*impl<E, At, Ch> ElementType for HtmlElement<E, At, Ch>
60where
61    E: ElementType,
62{
63    type Output = E::Output;
64
65    const TAG: &'static str = E::TAG;
66
67    const SELF_CLOSING: bool = E::SELF_CLOSING;
68
69    fn tag(&self) -> &str {
70        Self::TAG
71    }
72}*/
73
74#[cfg(not(erase_components))]
75impl<E, At, Ch, NewChild> ElementChild<NewChild> for HtmlElement<E, At, Ch>
76where
77    E: ElementWithChildren,
78    Ch: RenderHtml + next_tuple::NextTuple,
79    <Ch as next_tuple::NextTuple>::Output<NewChild::Output>: Render,
80
81    NewChild: IntoRender,
82    NewChild::Output: RenderHtml,
83{
84    type Output = HtmlElement<
85        E,
86        At,
87        <Ch as next_tuple::NextTuple>::Output<NewChild::Output>,
88    >;
89
90    fn child(self, child: NewChild) -> Self::Output {
91        HtmlElement {
92            #[cfg(any(debug_assertions, leptos_debuginfo))]
93            defined_at: self.defined_at,
94            tag: self.tag,
95            attributes: self.attributes,
96            children: self.children.next_tuple(child.into_render()),
97        }
98    }
99}
100
101#[cfg(erase_components)]
102impl<E, At, Ch, NewChild> ElementChild<NewChild> for HtmlElement<E, At, Ch>
103where
104    E: ElementWithChildren,
105    Ch: RenderHtml + NextChildren,
106
107    NewChild: IntoRender,
108    NewChild::Output: RenderHtml,
109{
110    type Output =
111        HtmlElement<E, At, crate::view::iterators::StaticVec<AnyView>>;
112
113    fn child(self, child: NewChild) -> Self::Output {
114        use crate::view::any_view::IntoAny;
115
116        HtmlElement {
117            #[cfg(any(debug_assertions, leptos_debuginfo))]
118            defined_at: self.defined_at,
119            tag: self.tag,
120            attributes: self.attributes,
121            children: self
122                .children
123                .next_children(child.into_render().into_any()),
124        }
125    }
126}
127
128#[cfg(erase_components)]
129trait NextChildren {
130    fn next_children(
131        self,
132        child: AnyView,
133    ) -> crate::view::iterators::StaticVec<AnyView>;
134}
135
136#[cfg(erase_components)]
137mod erased_tuples {
138    use super::*;
139    use crate::view::{any_view::IntoAny, iterators::StaticVec};
140
141    impl NextChildren for StaticVec<AnyView> {
142        fn next_children(mut self, child: AnyView) -> StaticVec<AnyView> {
143            self.0.push(child);
144            self
145        }
146    }
147
148    impl NextChildren for () {
149        fn next_children(self, child: AnyView) -> StaticVec<AnyView> {
150            vec![child].into()
151        }
152    }
153
154    impl<T: RenderHtml> NextChildren for (T,) {
155        fn next_children(self, child: AnyView) -> StaticVec<AnyView> {
156            vec![self.0.into_owned().into_any(), child].into()
157        }
158    }
159
160    macro_rules! impl_next_children_tuples {
161        ($($ty:ident),*) => {
162            impl<$($ty: RenderHtml),*> NextChildren for ($($ty,)*)
163             {
164                fn next_children(
165                    self, child: AnyView,
166                ) -> StaticVec<AnyView> {
167                    #[allow(non_snake_case)]
168                    let ($($ty,)*) = self;
169                    vec![$($ty.into_owned().into_any(),)* child].into()
170                }
171            }
172        };
173    }
174
175    impl_next_children_tuples!(AA, BB);
176    impl_next_children_tuples!(AA, BB, CC);
177    impl_next_children_tuples!(AA, BB, CC, DD);
178    impl_next_children_tuples!(AA, BB, CC, DD, EE);
179    impl_next_children_tuples!(AA, BB, CC, DD, EE, FF);
180    impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG);
181    impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH);
182    impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH, II);
183    impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH, II, JJ);
184    impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK);
185    impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL);
186    impl_next_children_tuples!(
187        AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM
188    );
189    impl_next_children_tuples!(
190        AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN
191    );
192    impl_next_children_tuples!(
193        AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO
194    );
195    impl_next_children_tuples!(
196        AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP
197    );
198    impl_next_children_tuples!(
199        AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ
200    );
201    impl_next_children_tuples!(
202        AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR
203    );
204    impl_next_children_tuples!(
205        AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
206        SS
207    );
208    impl_next_children_tuples!(
209        AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
210        SS, TT
211    );
212    impl_next_children_tuples!(
213        AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
214        SS, TT, UU
215    );
216    impl_next_children_tuples!(
217        AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
218        SS, TT, UU, VV
219    );
220    impl_next_children_tuples!(
221        AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
222        SS, TT, UU, VV, WW
223    );
224    impl_next_children_tuples!(
225        AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
226        SS, TT, UU, VV, WW, XX
227    );
228    impl_next_children_tuples!(
229        AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
230        SS, TT, UU, VV, WW, XX, YY
231    );
232}
233
234impl<E, At, Ch> AddAnyAttr for HtmlElement<E, At, Ch>
235where
236    E: ElementType + Send,
237    At: Attribute + Send,
238    Ch: RenderHtml + Send,
239{
240    type Output<SomeNewAttr: Attribute> =
241        HtmlElement<E, <At as NextAttribute>::Output<SomeNewAttr>, Ch>;
242
243    fn add_any_attr<NewAttr: Attribute>(
244        self,
245        attr: NewAttr,
246    ) -> Self::Output<NewAttr> {
247        let HtmlElement {
248            #[cfg(any(debug_assertions, leptos_debuginfo))]
249            defined_at,
250            tag,
251            attributes,
252            children,
253        } = self;
254        HtmlElement {
255            #[cfg(any(debug_assertions, leptos_debuginfo))]
256            defined_at,
257            tag,
258            attributes: attributes.add_any_attr(attr),
259            children,
260        }
261    }
262}
263
264/// Adds a child to the element.
265pub trait ElementChild<NewChild>
266where
267    NewChild: IntoRender,
268{
269    /// The type of the element, with the child added.
270    type Output;
271
272    /// Adds a child to an element.
273    fn child(self, child: NewChild) -> Self::Output;
274}
275
276/// An HTML element.
277pub trait ElementType: Send + 'static {
278    /// The underlying native widget type that this represents.
279    type Output;
280
281    /// The element's tag.
282    const TAG: &'static str;
283    /// Whether the element is self-closing.
284    const SELF_CLOSING: bool;
285    /// Whether the element's children should be escaped. This should be `true` except for elements
286    /// like `<style>` and `<script>`, which include other languages that should not use HTML
287    /// entity escaping.
288    const ESCAPE_CHILDREN: bool;
289    /// The element's namespace, if it is not HTML.
290    const NAMESPACE: Option<&'static str>;
291
292    /// The element's tag.
293    fn tag(&self) -> &str;
294}
295
296/// Denotes that the type that implements this has a particular HTML element type.
297pub trait HasElementType {
298    /// The element type.
299    type ElementType;
300}
301
302pub(crate) trait ElementWithChildren {}
303
304impl<E, At, Ch> HasElementType for HtmlElement<E, At, Ch>
305where
306    E: ElementType,
307{
308    type ElementType = E::Output;
309}
310
311impl<E, At, Ch> Render for HtmlElement<E, At, Ch>
312where
313    E: ElementType,
314    At: Attribute,
315    Ch: Render,
316{
317    type State = ElementState<At::State, Ch::State>;
318
319    fn rebuild(self, state: &mut Self::State) {
320        // check whether the tag is the same, for custom elements
321        // because this is const `false` for all other element types,
322        // the compiler should be able to optimize it out
323        if E::TAG.is_empty() {
324            // see https://github.com/leptos-rs/leptos/issues/4412
325            let new_tag = self.tag.tag();
326
327            // this is not particularly efficient, but it saves us from
328            // having to keep track of the tag name for every element state
329            let old_tag = state.el.tag_name();
330            if new_tag != old_tag {
331                let mut new_state = self.build();
332                state.insert_before_this(&mut new_state);
333                state.unmount();
334                *state = new_state;
335                return;
336            }
337        }
338
339        // rebuild attributes and children for any element
340        let ElementState {
341            attrs, children, ..
342        } = state;
343        self.attributes.rebuild(attrs);
344        if let Some(children) = children {
345            self.children.rebuild(children);
346        }
347    }
348
349    fn build(self) -> Self::State {
350        let el = Rndr::create_element(self.tag.tag(), E::NAMESPACE);
351
352        let attrs = self.attributes.build(&el);
353
354        let children = if E::SELF_CLOSING {
355            None
356        } else {
357            let mut children = self.children.build();
358            children.mount(&el, None);
359            Some(children)
360        };
361
362        ElementState {
363            el,
364            attrs,
365            children,
366        }
367    }
368}
369
370impl<E, At, Ch> RenderHtml for HtmlElement<E, At, Ch>
371where
372    E: ElementType + Send,
373    At: Attribute + Send,
374    Ch: RenderHtml + Send,
375{
376    type AsyncOutput = HtmlElement<E, At::AsyncOutput, Ch::AsyncOutput>;
377    type Owned = HtmlElement<E, At::CloneableOwned, Ch::Owned>;
378
379    const MIN_LENGTH: usize = if E::SELF_CLOSING {
380        3 // < ... />
381        + E::TAG.len()
382        + At::MIN_LENGTH
383    } else {
384        2 // < ... >
385        + E::TAG.len()
386        + At::MIN_LENGTH
387        + Ch::MIN_LENGTH
388        + 3 // </ ... >
389        + E::TAG.len()
390    };
391
392    fn dry_resolve(&mut self) {
393        self.attributes.dry_resolve();
394        self.children.dry_resolve();
395    }
396
397    async fn resolve(self) -> Self::AsyncOutput {
398        let (attributes, children) =
399            join(self.attributes.resolve(), self.children.resolve()).await;
400        HtmlElement {
401            #[cfg(any(debug_assertions, leptos_debuginfo))]
402            defined_at: self.defined_at,
403            tag: self.tag,
404            attributes,
405            children,
406        }
407    }
408
409    fn html_len(&self) -> usize {
410        if E::SELF_CLOSING {
411            3 // < ... />
412        + E::TAG.len()
413        + self.attributes.html_len()
414        } else {
415            2 // < ... >
416        + E::TAG.len()
417        + self.attributes.html_len()
418        + self.children.html_len()
419        + 3 // </ ... >
420        + E::TAG.len()
421        }
422    }
423
424    fn to_html_with_buf(
425        self,
426        buf: &mut String,
427        position: &mut Position,
428        _escape: bool,
429        mark_branches: bool,
430        extra_attributes: Vec<AnyAttribute>,
431    ) {
432        // opening tag
433        buf.push('<');
434        buf.push_str(self.tag.tag());
435
436        let inner_html =
437            attributes_to_html((self.attributes, extra_attributes), buf);
438
439        buf.push('>');
440
441        if !E::SELF_CLOSING {
442            if !inner_html.is_empty() {
443                buf.push_str(&inner_html);
444            } else if Ch::EXISTS {
445                // children
446                *position = Position::FirstChild;
447                self.children.to_html_with_buf(
448                    buf,
449                    position,
450                    E::ESCAPE_CHILDREN,
451                    mark_branches,
452                    vec![],
453                );
454            }
455
456            // closing tag
457            buf.push_str("</");
458            buf.push_str(self.tag.tag());
459            buf.push('>');
460        }
461        *position = Position::NextChild;
462    }
463
464    fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
465        self,
466        buffer: &mut StreamBuilder,
467        position: &mut Position,
468        _escape: bool,
469        mark_branches: bool,
470        extra_attributes: Vec<AnyAttribute>,
471    ) where
472        Self: Sized,
473    {
474        let mut buf = String::with_capacity(Self::MIN_LENGTH);
475        // opening tag
476        buf.push('<');
477        buf.push_str(self.tag.tag());
478
479        let inner_html =
480            attributes_to_html((self.attributes, extra_attributes), &mut buf);
481
482        buf.push('>');
483        buffer.push_sync(&buf);
484
485        if !E::SELF_CLOSING {
486            // children
487            *position = Position::FirstChild;
488            if !inner_html.is_empty() {
489                buffer.push_sync(&inner_html);
490            } else if Ch::EXISTS {
491                self.children.to_html_async_with_buf::<OUT_OF_ORDER>(
492                    buffer,
493                    position,
494                    E::ESCAPE_CHILDREN,
495                    mark_branches,
496                    vec![],
497                );
498            }
499
500            // closing tag
501            let mut buf = String::with_capacity(3 + E::TAG.len());
502            buf.push_str("</");
503            buf.push_str(self.tag.tag());
504            buf.push('>');
505            buffer.push_sync(&buf);
506        }
507        *position = Position::NextChild;
508    }
509
510    fn hydrate<const FROM_SERVER: bool>(
511        self,
512        cursor: &Cursor,
513        position: &PositionState,
514    ) -> Self::State {
515        // non-Static custom elements need special support in templates
516        // because they haven't been inserted type-wise
517        if E::TAG.is_empty() && !FROM_SERVER {
518            panic!("Custom elements are not supported in ViewTemplate.");
519        }
520
521        // codegen optimisation:
522        fn inner_1(
523            cursor: &Cursor,
524            position: &PositionState,
525            tag_name: &str,
526            #[cfg(any(debug_assertions, leptos_debuginfo))]
527            defined_at: &'static std::panic::Location<'static>,
528        ) -> crate::renderer::types::Element {
529            #[cfg(any(debug_assertions, leptos_debuginfo))]
530            {
531                set_currently_hydrating(Some(defined_at));
532            }
533
534            let curr_position = position.get();
535            if curr_position == Position::FirstChild {
536                cursor.child();
537            } else if curr_position != Position::Current {
538                cursor.sibling();
539            }
540            crate::renderer::types::Element::cast_from(cursor.current())
541                .unwrap_or_else(|| {
542                    failed_to_cast_element(tag_name, cursor.current())
543                })
544        }
545        let el = inner_1(
546            cursor,
547            position,
548            E::TAG,
549            #[cfg(any(debug_assertions, leptos_debuginfo))]
550            self.defined_at,
551        );
552
553        let attrs = self.attributes.hydrate::<FROM_SERVER>(&el);
554
555        // hydrate children
556        let children = if !Ch::EXISTS || !E::ESCAPE_CHILDREN {
557            None
558        } else {
559            position.set(Position::FirstChild);
560            Some(self.children.hydrate::<FROM_SERVER>(cursor, position))
561        };
562
563        // codegen optimisation:
564        fn inner_2(
565            cursor: &Cursor,
566            position: &PositionState,
567            el: &crate::renderer::types::Element,
568        ) {
569            // go to next sibling
570            cursor.set(
571                <crate::renderer::types::Element as AsRef<
572                    crate::renderer::types::Node,
573                >>::as_ref(el)
574                .clone(),
575            );
576            position.set(Position::NextChild);
577        }
578        inner_2(cursor, position, &el);
579
580        ElementState {
581            el,
582            attrs,
583            children,
584        }
585    }
586
587    async fn hydrate_async(
588        self,
589        cursor: &Cursor,
590        position: &PositionState,
591    ) -> Self::State {
592        // codegen optimisation:
593        fn inner_1(
594            cursor: &Cursor,
595            position: &PositionState,
596            tag_name: &str,
597            #[cfg(any(debug_assertions, leptos_debuginfo))]
598            defined_at: &'static std::panic::Location<'static>,
599        ) -> crate::renderer::types::Element {
600            #[cfg(any(debug_assertions, leptos_debuginfo))]
601            {
602                set_currently_hydrating(Some(defined_at));
603            }
604
605            let curr_position = position.get();
606            if curr_position == Position::FirstChild {
607                cursor.child();
608            } else if curr_position != Position::Current {
609                cursor.sibling();
610            }
611            crate::renderer::types::Element::cast_from(cursor.current())
612                .unwrap_or_else(|| {
613                    failed_to_cast_element(tag_name, cursor.current())
614                })
615        }
616        let el = inner_1(
617            cursor,
618            position,
619            E::TAG,
620            #[cfg(any(debug_assertions, leptos_debuginfo))]
621            self.defined_at,
622        );
623
624        let attrs = self.attributes.hydrate::<true>(&el);
625
626        // hydrate children
627        let children = if !Ch::EXISTS || !E::ESCAPE_CHILDREN {
628            None
629        } else {
630            position.set(Position::FirstChild);
631            Some(self.children.hydrate_async(cursor, position).await)
632        };
633
634        // codegen optimisation:
635        fn inner_2(
636            cursor: &Cursor,
637            position: &PositionState,
638            el: &crate::renderer::types::Element,
639        ) {
640            // go to next sibling
641            cursor.set(
642                <crate::renderer::types::Element as AsRef<
643                    crate::renderer::types::Node,
644                >>::as_ref(el)
645                .clone(),
646            );
647            position.set(Position::NextChild);
648        }
649        inner_2(cursor, position, &el);
650
651        ElementState {
652            el,
653            attrs,
654            children,
655        }
656    }
657
658    fn into_owned(self) -> Self::Owned {
659        HtmlElement {
660            #[cfg(any(debug_assertions, leptos_debuginfo))]
661            defined_at: self.defined_at,
662            tag: self.tag,
663            attributes: self.attributes.into_cloneable_owned(),
664            children: self.children.into_owned(),
665        }
666    }
667}
668
669/// Renders an [`Attribute`] (which can be one or more HTML attributes) into an HTML buffer.
670pub fn attributes_to_html<At>(attr: At, buf: &mut String) -> String
671where
672    At: Attribute,
673{
674    // `class` and `style` are created first, and pushed later
675    // this is because they can be filled by a mixture of values that include
676    // either the whole value (`class="..."` or `style="..."`) and individual
677    // classes and styles (`class:foo=true` or `style:height="40px"`), so they
678    // need to be filled during the whole attribute-creation process and then
679    // added
680
681    // String doesn't allocate until the first push, so this is cheap if there
682    // is no class or style on an element
683    let mut class = String::new();
684    let mut style = String::new();
685    let mut inner_html = String::new();
686
687    // inject regular attributes, and fill class and style
688    attr.to_html(buf, &mut class, &mut style, &mut inner_html);
689
690    if !class.is_empty() {
691        buf.push(' ');
692        buf.push_str("class=\"");
693        buf.push_str(&escape_attr(class.trim_start().trim_end()));
694        buf.push('"');
695    }
696    if !style.is_empty() {
697        buf.push(' ');
698        buf.push_str("style=\"");
699        buf.push_str(&escape_attr(style.trim_start().trim_end()));
700        buf.push('"');
701    }
702
703    inner_html
704}
705
706/// The retained view state for an HTML element.
707pub struct ElementState<At, Ch> {
708    pub(crate) el: crate::renderer::types::Element,
709    pub(crate) attrs: At,
710    pub(crate) children: Option<Ch>,
711}
712
713impl<At, Ch> Deref for ElementState<At, Ch> {
714    type Target = crate::renderer::types::Element;
715
716    fn deref(&self) -> &Self::Target {
717        &self.el
718    }
719}
720
721impl<At, Ch> Mountable for ElementState<At, Ch> {
722    fn unmount(&mut self) {
723        Rndr::remove(&self.el);
724    }
725
726    fn mount(
727        &mut self,
728        parent: &crate::renderer::types::Element,
729        marker: Option<&crate::renderer::types::Node>,
730    ) {
731        Rndr::insert_node(parent, &self.el, marker);
732    }
733
734    fn try_mount(
735        &mut self,
736        parent: &crate::renderer::types::Element,
737        marker: Option<&crate::renderer::types::Node>,
738    ) -> bool {
739        Rndr::try_insert_node(parent, &self.el, marker)
740    }
741
742    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
743        // codegen optimisation:
744        fn inner(
745            element: &crate::renderer::types::Element,
746            child: &mut dyn Mountable,
747        ) -> bool {
748            if let Some(parent) = Rndr::get_parent(element)
749                .and_then(crate::renderer::types::Element::cast_from)
750            {
751                child.mount(&parent, Some(element));
752                true
753            } else {
754                false
755            }
756        }
757        inner(&self.el, child)
758    }
759
760    fn elements(&self) -> Vec<crate::renderer::types::Element> {
761        // codegen optimisation:
762        fn inner(
763            element: &crate::renderer::types::Element,
764        ) -> Vec<crate::renderer::types::Element> {
765            vec![element.clone()]
766        }
767        inner(&self.el)
768    }
769}
770
771impl<E, At, Ch> ToTemplate for HtmlElement<E, At, Ch>
772where
773    E: ElementType,
774    At: Attribute + ToTemplate,
775    Ch: Render + ToTemplate,
776{
777    const TEMPLATE: &'static str = str_from_buffer(&const_concat(&[
778        "<",
779        E::TAG,
780        At::TEMPLATE,
781        str_from_buffer(&const_concat_with_prefix(
782            &[At::CLASS],
783            " class=\"",
784            "\"",
785        )),
786        str_from_buffer(&const_concat_with_prefix(
787            &[At::STYLE],
788            " style=\"",
789            "\"",
790        )),
791        ">",
792        Ch::TEMPLATE,
793        "</",
794        E::TAG,
795        ">",
796    ]));
797
798    #[allow(unused)] // the variables `class` and `style` might be used, but only with `nightly` feature
799    fn to_template(
800        buf: &mut String,
801        class: &mut String,
802        style: &mut String,
803        inner_html: &mut String,
804        position: &mut Position,
805    ) {
806        // for custom elements without type known at compile time, do nothing
807        if !E::TAG.is_empty() {
808            // opening tag and attributes
809            let mut class = String::new();
810            let mut style = String::new();
811            let mut inner_html = String::new();
812
813            buf.push('<');
814            buf.push_str(E::TAG);
815            <At as ToTemplate>::to_template_attribute(
816                buf,
817                &mut class,
818                &mut style,
819                &mut inner_html,
820                position,
821            );
822
823            if !class.is_empty() {
824                buf.push(' ');
825                buf.push_str("class=\"");
826                buf.push_str(class.trim_start().trim_end());
827                buf.push('"');
828            }
829            if !style.is_empty() {
830                buf.push(' ');
831                buf.push_str("style=\"");
832                buf.push_str(style.trim_start().trim_end());
833                buf.push('"');
834            }
835            buf.push('>');
836
837            // children
838            *position = Position::FirstChild;
839            class.clear();
840            style.clear();
841            inner_html.clear();
842            Ch::to_template(
843                buf,
844                &mut class,
845                &mut style,
846                &mut inner_html,
847                position,
848            );
849
850            // closing tag
851            buf.push_str("</");
852            buf.push_str(E::TAG);
853            buf.push('>');
854            *position = Position::NextChild;
855        }
856    }
857}
858/*
859#[cfg(all(test, feature = "testing"))]
860mod tests {
861    #[cfg(all(feature = "nightly", rustc_nightly))]
862    use super::RenderHtml;
863    use super::{main, p, HtmlElement};
864    use crate::{
865        html::{
866            attribute::global::GlobalAttributes,
867            element::{em, ElementChild, Main},
868        },
869        renderer::mock_dom::MockDom,
870        view::Render,
871    };
872
873    #[test]
874    fn mock_dom_creates_element() {
875        let el: HtmlElement<Main, _, _, MockDom> =
876            main().child(p().id("test").lang("en").child("Hello, world!"));
877        let el = el.build();
878        assert_eq!(
879            el.el.to_debug_html(),
880            "<main><p id=\"test\" lang=\"en\">Hello, world!</p></main>"
881        );
882    }
883
884    #[test]
885    fn mock_dom_creates_element_with_several_children() {
886        let el: HtmlElement<Main, _, _, MockDom> = main().child(p().child((
887            "Hello, ",
888            em().child("beautiful"),
889            " world!",
890        )));
891        let el = el.build();
892        assert_eq!(
893            el.el.to_debug_html(),
894            "<main><p>Hello, <em>beautiful</em> world!</p></main>"
895        );
896    }
897
898    #[cfg(all(feature = "nightly", rustc_nightly))]
899    #[test]
900    fn html_render_allocates_appropriate_buffer() {
901        use crate::view::static_types::Static;
902
903        let el: HtmlElement<Main, _, _, MockDom> = main().child(p().child((
904            Static::<"Hello, ">,
905            em().child(Static::<"beautiful">),
906            Static::<" world!">,
907        )));
908        let allocated_len = el.html_len();
909        let html = el.to_html();
910        assert_eq!(
911            html,
912            "<main><p>Hello, <em>beautiful</em> world!</p></main>"
913        );
914        assert_eq!(html.len(), allocated_len);
915    }
916}
917 */