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
//! This module contains the implementation of a virtual text node `VText`.

use super::{Reform, VDiff, VNode};
use html::{Component, Scope};
use std::cmp::PartialEq;
use std::fmt;
use std::marker::PhantomData;
use web_sys::{window, Node, Text};

/// A type for a virtual
/// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode)
/// represenation.
pub struct VText<COMP: Component> {
    /// Contains a text of the node.
    pub text: String,
    /// A reference to the `TextNode`.
    pub reference: Option<Text>,
    _comp: PhantomData<COMP>,
}

impl<COMP: Component> VText<COMP> {
    /// Creates new virtual text node with a content.
    pub fn new(text: String) -> Self {
        VText {
            text,
            reference: None,
            _comp: PhantomData,
        }
    }
}

impl<COMP: Component> VDiff for VText<COMP> {
    type Component = COMP;

    /// Remove VTag from parent.
    fn detach(&mut self, parent: &Node) -> Option<Node> {
        let node = self
            .reference
            .take()
            .expect("tried to remove not rendered VText from DOM");
        let sibling = node.next_sibling();
        if parent.remove_child(&node).is_err() {
            warn!("Node not found to remove VText");
        }
        sibling
    }

    /// Renders virtual node over existent `TextNode`, but
    /// only if value of text had changed.
    /// Parameter `precursor` is necesssary for `VTag` and `VList` which
    /// has children and renders them.
    fn apply(
        &mut self,
        parent: &Node,
        _: Option<&Node>,
        opposite: Option<VNode<Self::Component>>,
        _: &Scope<Self::Component>,
    ) -> Option<Node> {
        assert!(
            self.reference.is_none(),
            "reference is ignored so must not be set"
        );
        let reform = {
            match opposite {
                // If element matched this type
                Some(VNode::VText(mut vtext)) => {
                    self.reference = vtext.reference.take();
                    if self.text != vtext.text {
                        if let Some(ref element) = self.reference {
                            element.set_node_value(Some(&self.text));
                        }
                    }
                    Reform::Keep
                }
                Some(mut vnode) => {
                    let node = vnode.detach(parent);
                    Reform::Before(node)
                }
                None => Reform::Before(None),
            }
        };
        match reform {
            Reform::Keep => {}
            Reform::Before(node) => {
                let element = window()
                    .expect("context needs a window")
                    .document()
                    .expect("window needs a document")
                    .create_text_node(&self.text);
                if let Some(sibling) = node {
                    parent
                        .insert_before(&element, Some(&sibling))
                        .expect("can't insert text before sibling");
                } else {
                    parent
                        .append_child(&element)
                        .expect("could not append child to node");
                }
                self.reference = Some(element);
            }
        }
        self.reference.as_ref().map(|t| t.to_owned().into())
    }
}

impl<COMP: Component> fmt::Debug for VText<COMP> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "VText {{ text: {} }}", self.text)
    }
}

impl<COMP: Component> PartialEq for VText<COMP> {
    fn eq(&self, other: &VText<COMP>) -> bool {
        self.text == other.text
    }
}