1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use super::{
    add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
    RenderHtml, ToTemplate,
};
use crate::{
    html::attribute::Attribute, hydration::Cursor, renderer::DomRenderer,
};
use std::marker::PhantomData;

/// A view wrapper that uses a `<template>` node to optimize DOM node creation.
///
/// Rather than creating all of the DOM nodes each time it is built, this template will create a
/// single `<template>` node once, then use `.cloneNode(true)` to clone that entire tree, and
/// hydrate it to add event listeners and interactivity for this instance.
pub struct ViewTemplate<V, R> {
    view: V,
    rndr: PhantomData<R>,
}

impl<V, R> ViewTemplate<V, R>
where
    V: Render<R> + ToTemplate + 'static,
    R: DomRenderer,
{
    /// Creates a new view template.
    pub fn new(view: V) -> Self {
        Self {
            view,
            rndr: PhantomData,
        }
    }

    fn to_template() -> R::TemplateElement {
        R::get_template::<V>()
    }
}

impl<V, R> Render<R> for ViewTemplate<V, R>
where
    V: Render<R> + RenderHtml<R> + ToTemplate + 'static,
    V::State: Mountable<R>,
    R: DomRenderer,
{
    type State = V::State;

    // TODO try_build/try_rebuild()

    fn build(self) -> Self::State {
        let tpl = Self::to_template();
        let contents = R::clone_template(&tpl);
        self.view
            .hydrate::<false>(&Cursor::new(contents), &Default::default())
    }

    fn rebuild(self, state: &mut Self::State) {
        self.view.rebuild(state)
    }
}

impl<V, R> AddAnyAttr<R> for ViewTemplate<V, R>
where
    V: RenderHtml<R> + ToTemplate + 'static,
    V::State: Mountable<R>,
    R: DomRenderer,
{
    type Output<SomeNewAttr: Attribute<R>> = ViewTemplate<V, R>;

    fn add_any_attr<NewAttr: Attribute<R>>(
        self,
        _attr: NewAttr,
    ) -> Self::Output<NewAttr>
    where
        Self::Output<NewAttr>: RenderHtml<R>,
    {
        panic!("AddAnyAttr not supported on ViewTemplate");
    }
}

impl<V, R> RenderHtml<R> for ViewTemplate<V, R>
where
    V: RenderHtml<R> + ToTemplate + 'static,
    V::State: Mountable<R>,
    R: DomRenderer,
{
    type AsyncOutput = V::AsyncOutput;

    const MIN_LENGTH: usize = V::MIN_LENGTH;

    fn to_html_with_buf(
        self,
        buf: &mut String,
        position: &mut Position,
        escape: bool,
        mark_branches: bool,
    ) {
        self.view
            .to_html_with_buf(buf, position, escape, mark_branches)
    }

    fn hydrate<const FROM_SERVER: bool>(
        self,
        cursor: &Cursor<R>,
        position: &PositionState,
    ) -> Self::State {
        self.view.hydrate::<FROM_SERVER>(cursor, position)
    }

    fn dry_resolve(&mut self) {
        todo!()
    }

    async fn resolve(self) -> Self::AsyncOutput {
        todo!()
    }
}

impl<V, R> ToTemplate for ViewTemplate<V, R>
where
    V: RenderHtml<R> + ToTemplate + 'static,
    V::State: Mountable<R>,
    R: DomRenderer,
{
    const TEMPLATE: &'static str = V::TEMPLATE;

    fn to_template(
        buf: &mut String,
        class: &mut String,
        style: &mut String,
        inner_html: &mut String,
        position: &mut Position,
    ) {
        V::to_template(buf, class, style, inner_html, position);
    }
}