perspective_viewer/custom_elements/
column_dropdown.rs1use std::collections::HashSet;
14
15use perspective_client::clone;
16use perspective_client::config::Expression;
17use perspective_js::json;
18use perspective_js::utils::global;
19use wasm_bindgen::JsCast;
20use wasm_bindgen::prelude::*;
21use web_sys::*;
22use yew::html::ImplicitClone;
23use yew::{Callback, props};
24
25use crate::components::column_dropdown::*;
26use crate::components::column_selector::InPlaceColumn;
27use crate::custom_elements::modal::*;
28use crate::session::Session;
29use crate::*;
30
31#[wasm_bindgen]
32#[derive(Clone)]
33pub struct ColumnDropDownElement {
34 modal: ModalElement<ColumnDropDown>,
35 session: Session,
36}
37
38impl ImplicitClone for ColumnDropDownElement {}
39
40impl ColumnDropDownElement {
41 pub fn new(session: Session) -> Self {
42 let dropdown = global::document()
43 .create_element("perspective-dropdown")
44 .unwrap()
45 .unchecked_into::<HtmlElement>();
46
47 let props = props!(ColumnDropDownProps {});
48 let modal = ModalElement::new(dropdown, props, false, None);
49 Self { modal, session }
50 }
51
52 pub fn autocomplete(
53 &self,
54 target: HtmlInputElement,
55 exclude: HashSet<String>,
56 callback: Callback<InPlaceColumn>,
57 ) -> Option<()> {
58 let input = target.value();
59 let metadata = self.session.metadata();
60 let mut values: Vec<InPlaceColumn> = vec![];
61 let small_input = input.to_lowercase();
62 for col in metadata.get_table_columns()? {
63 if !exclude.contains(col) && col.to_lowercase().contains(&small_input) {
64 values.push(InPlaceColumn::Column(col.to_owned()));
65 }
66 }
67
68 for col in self.session.metadata().get_expression_columns() {
69 if !exclude.contains(col) && col.to_lowercase().contains(&small_input) {
70 values.push(InPlaceColumn::Column(col.to_owned()));
71 }
72 }
73
74 clone!(self.modal, self.session);
75 ApiFuture::spawn(async move {
76 if !exclude.contains(&input) {
77 let is_expr = session.validate_expr(&input).await?.is_none();
78
79 if is_expr {
80 values.push(InPlaceColumn::Expression(Expression::new(
81 None,
82 input.into(),
83 )));
84 }
85 }
86
87 let classes = modal.custom_element.class_list();
88 let no_results = json!(["no-results"]);
89 if values.is_empty() {
90 classes.add(&no_results).unwrap();
91 } else {
92 classes.remove(&no_results).unwrap();
93 }
94
95 modal.send_message_batch(vec![
96 ColumnDropDownMsg::SetCallback(callback),
97 ColumnDropDownMsg::SetValues(values, target.get_bounding_client_rect().width()),
98 ]);
99
100 modal.open(target.unchecked_into(), None).await
101 });
102
103 Some(())
104 }
105
106 pub fn item_select(&self) {
107 self.modal.send_message(ColumnDropDownMsg::ItemSelect);
108 }
109
110 pub fn item_down(&self) {
111 self.modal.send_message(ColumnDropDownMsg::ItemDown);
112 }
113
114 pub fn item_up(&self) {
115 self.modal.send_message(ColumnDropDownMsg::ItemUp);
116 }
117
118 pub fn hide(&self) -> ApiResult<()> {
119 self.modal.hide()
120 }
121
122 pub fn connected_callback(&self) {}
123}