Skip to main content

perspective_viewer/components/
empty_row.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
5// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13use std::collections::HashSet;
14use std::rc::Rc;
15
16use derivative::Derivative;
17use perspective_client::clone;
18use wasm_bindgen::JsCast;
19use web_sys::*;
20use yew::prelude::*;
21
22use crate::components::filter_dropdown::FilterDropDownElement;
23
24#[derive(Properties, Derivative)]
25#[derivative(Debug)]
26pub struct EmptyRowProps {
27    #[derivative(Debug = "ignore")]
28    pub dropdown: Rc<FilterDropDownElement>,
29    pub exclude: HashSet<String>,
30    pub on_select: Callback<String>,
31    pub focused: bool,
32    pub index: usize,
33    pub set_focused_index: Callback<Option<usize>>,
34    pub value: String,
35    pub column_name: String,
36}
37
38impl PartialEq for EmptyRowProps {
39    fn eq(&self, _other: &Self) -> bool {
40        false
41    }
42}
43
44pub enum EmptyRowMsg {
45    KeyDown(u32),
46    Blur,
47    Input(String),
48    Focus,
49}
50
51use EmptyRowMsg::*;
52
53#[derive(Default)]
54pub struct EmptyRow {
55    input_ref: NodeRef,
56}
57
58impl Component for EmptyRow {
59    type Message = EmptyRowMsg;
60    type Properties = EmptyRowProps;
61
62    fn view(&self, ctx: &Context<Self>) -> Html {
63        let onblur = ctx.link().callback(|_| Blur);
64        let oninput = ctx.link().callback(|e: InputEvent| {
65            let value = e
66                .target()
67                .unwrap()
68                .unchecked_into::<HtmlInputElement>()
69                .value();
70            Input(value)
71        });
72        let onkeydown = ctx
73            .link()
74            .callback(|event: KeyboardEvent| KeyDown(event.key_code()));
75
76        if ctx.props().focused {
77            // do this on the next render cycle so we know the ref is there
78            ctx.link()
79                .send_message_batch(vec![Focus, Input(ctx.props().value.clone())]);
80        }
81        let onfocus = {
82            clone!(ctx.props().value);
83            ctx.link().callback(move |_| Input(value.clone()))
84        };
85
86        html! {
87            <div class="pivot-column column-empty">
88                <input
89                    spellcheck="false"
90                    ref={self.input_ref.clone()}
91                    {onblur}
92                    {onkeydown}
93                    {oninput}
94                    {onfocus}
95                    class="column-empty-input"
96                />
97            </div>
98        }
99    }
100
101    fn changed(&mut self, _ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
102        if let Some(elem) = self.input_ref.cast::<HtmlInputElement>() {
103            elem.blur().unwrap();
104        }
105        false
106    }
107
108    fn destroy(&mut self, ctx: &Context<Self>) {
109        ctx.props().dropdown.hide().unwrap();
110    }
111
112    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
113        let p = ctx.props();
114        match msg {
115            Focus => {
116                if let Some(elem) = self.input_ref.cast::<HtmlInputElement>() {
117                    elem.set_value(&ctx.props().value);
118                    elem.focus().unwrap();
119                }
120
121                false
122            },
123            Blur => {
124                p.dropdown.hide().unwrap();
125                if let Some(elem) = self.input_ref.cast::<HtmlInputElement>() {
126                    elem.set_value("");
127                }
128                p.set_focused_index.emit(Some(p.index + 1));
129
130                false
131            },
132            KeyDown(40) => {
133                p.dropdown.item_down();
134                false
135            },
136            KeyDown(38) => {
137                p.dropdown.item_up();
138                false
139            },
140            KeyDown(13) => {
141                p.dropdown.item_select();
142                p.dropdown.hide().unwrap();
143                p.set_focused_index.emit(Some(p.index + 1));
144                true
145            },
146            KeyDown(_) => false,
147            Input(value) => {
148                if let Some(elem) = self.input_ref.cast::<HtmlElement>() {
149                    ctx.props().dropdown.autocomplete(
150                        (ctx.props().index, ctx.props().column_name.clone()),
151                        value,
152                        ctx.props().exclude.clone(),
153                        elem,
154                        ctx.props().on_select.clone(),
155                    );
156                }
157
158                false
159            },
160        }
161    }
162
163    fn create(_ctx: &Context<Self>) -> Self {
164        Self::default()
165    }
166}