use std::{borrow::Cow, cell::OnceCell};
use crate::{
WdLsData,
element::{ElementDefWrapper, EventParameterMap},
error::WebDynproError,
};
use self::item::{ListBoxItemDefWrapper, ListBoxItemInfo};
macro_rules! def_listbox_subset {
[$({
$(#[$attr:meta])*
$name:ident = $id:literal,
$(#[$def_attr:meta])*
$def_name:ident
}),+ $(,)?] => {$(
$(#[$def_attr])*
#[derive(Clone, Debug)]
pub struct $def_name {
id: std::borrow::Cow<'static, str>
}
impl $def_name {
pub const fn new(id: &'static str) -> Self {
Self {
id: std::borrow::Cow::Borrowed(id)
}
}
}
impl<'body> $crate::element::definition::ElementDefinition<'body> for $def_name {
type Element = $name<'body>;
fn new_dynamic(id: String) -> Self {
Self {
id: id.into()
}
}
fn from_ref(element_ref: scraper::ElementRef<'_>) -> Result<Self, $crate::error::WebDynproError> {
let id = element_ref.value().id().ok_or($crate::error::BodyError::InvalidElement)?;
Ok(Self {
id: id.to_string().into()
})
}
fn id(&self) -> &str {
&self.id
}
fn id_cow(&self) -> Cow<'static, str> {
self.id.clone()
}
}
$(#[$attr])*
#[derive(Debug)]
pub struct $name<'a>($crate::element::selection::list_box::ListBox<'a>);
impl<'a> $name<'a> {
pub fn list_box(&self) -> &$crate::element::selection::list_box::ListBox<'a> {
&self.0
}
}
impl<'a> $crate::element::Element<'a> for $name<'a> {
const CONTROL_ID: &'static str = $id;
const ELEMENT_NAME: &'static str = "ListBox";
type ElementLSData = ListBoxLSData;
type Def = $def_name;
fn lsdata(&self) -> &Self::ElementLSData {
self.list_box().lsdata
.get_or_init(|| {
let lsdata_attr = self.element_ref().value().attr("lsdata").unwrap_or("");
let Ok(lsdata_obj) = $crate::element::utils::parse_lsdata(lsdata_attr).inspect_err(|e| tracing::warn!(?e, "failed to parse lsdata")) else {
return ListBoxLSData::default();
};
serde_json::from_value::<Self::ElementLSData>(lsdata_obj).unwrap_or(ListBoxLSData::default())
})
}
fn from_ref(elem_def: &impl $crate::element::definition::ElementDefinition<'a>, element: scraper::ElementRef<'a>) -> Result<Self, $crate::error::WebDynproError> {
Ok(Self::new($crate::element::definition::ElementDefinition::id_cow(elem_def), element))
}
fn id(&self) -> &str {
&self.list_box().id
}
fn element_ref(&self) -> &scraper::ElementRef<'a> {
&self.list_box().element_ref
}
fn wrap(self) -> $crate::element::ElementWrapper<'a> {
$crate::element::ElementWrapper::$name(self)
}
fn children(&self) -> Vec<$crate::element::ElementWrapper<'a>> {
$crate::element::utils::children_element(self.element_ref().clone())
}
}
impl<'a> $crate::element::Interactable<'a> for $name<'a> {
fn lsevents(&self) -> Option<&EventParameterMap> {
self.list_box().lsevents
.get_or_init(|| {
let lsevents_attr = self.list_box().element_ref.value().attr("lsevents").unwrap_or("");
$crate::element::utils::parse_lsevents(lsevents_attr).inspect_err(|e| tracing::warn!(?e, "failed to parse lsevents")).ok()
})
.as_ref()
}
}
impl<'a> $name<'a> {
#[doc = concat!("새로운 [`", stringify!($name), "`] 을 반환합니다.")]
pub const fn new(id: std::borrow::Cow<'static, str>, element_ref: scraper::ElementRef<'a>) -> Self {
Self($crate::element::selection::list_box::ListBox::new(id, element_ref))
}
}
inventory::submit! {
$crate::element::registry::ElementRegistration::new(
$id,
|id, element_ref| {
use $crate::element::Element;
use $crate::element::definition::ElementDefinition;
let def = $def_name::new_dynamic(id);
Ok($name::from_ref(&def, element_ref)?.wrap())
},
)
}
)+
#[derive(Clone, Debug)]
pub enum ListBoxDefWrapper {
$(
$(#[$attr])*
$name($def_name),
)+
}
impl ListBoxDefWrapper {
pub fn from_def(element_def: $crate::element::ElementDefWrapper) -> Option<ListBoxDefWrapper> {
match element_def {
$($crate::element::ElementDefWrapper::$name(elem) => Some(ListBoxDefWrapper::$name(elem)),)+
_ => None
}
}
pub fn value<'body>(&self, parser: &'body $crate::element::parser::ElementParser) -> Result<ListBoxWrapper<'body>, $crate::error::WebDynproError> {
match self {
$(ListBoxDefWrapper::$name(def) => {
let raw_element = parser.element_from_def(def)?;
let elem_wrapper = $crate::element::Element::wrap(raw_element);
Ok(ListBoxWrapper::from_elements(elem_wrapper).ok_or($crate::error::BodyError::InvalidElement)?)
},)+
}
}
}
#[derive(Debug)]
pub enum ListBoxWrapper<'body> {
$(
$(#[$attr])*
$name($name<'body>),
)+
}
impl<'body> ListBoxWrapper<'body> {
pub fn from_elements(elements: $crate::element::ElementWrapper<'body>) -> Option<ListBoxWrapper<'body>> {
match elements {
$($crate::element::ElementWrapper::$name(elem) => Some(ListBoxWrapper::$name(elem)),)+
_ => None
}
}
pub fn unwrap(&'body self) -> &'body $crate::element::selection::list_box::ListBox<'body> {
match self {
$(ListBoxWrapper::$name(elem) => elem.list_box(),)+
}
}
}
};
}
#[derive(Debug)]
pub struct ListBox<'a> {
id: Cow<'static, str>,
element_ref: scraper::ElementRef<'a>,
lsdata: OnceCell<ListBoxLSData>,
lsevents: OnceCell<Option<EventParameterMap>>,
items: OnceCell<Vec<ListBoxItemDefWrapper>>,
}
def_listbox_subset![
{
#[doc = "팝업 형태로 표시되는 [`ListBox`]"]
ListBoxPopup = "LIB_P",
#[doc = "[`ListBoxPopup`]의 정의"]
ListBoxPopupDef
},
{
#[doc = "팝업 형태로 표시되며 데이터 구조가 있는 [`ListBox`]"]
ListBoxPopupJson = "LIB_PJ",
#[doc = "[`ListBoxPopupJson`]의 정의"]
ListBoxPopupJsonDef
},
{
#[doc = "팝업 형태로 표시되며 필터 입력 상자가 있는 [`ListBox`]"]
ListBoxPopupFiltered = "LIB_PS",
#[doc = "[`ListBoxPopupFiltered`]의 정의"]
ListBoxPopupFilteredDef
},
{
#[doc = "팝업 형태로 표시되며 데이터 구조가 있고 필터 입력 상자가 있는 [`ListBox`]"]
ListBoxPopupJsonFiltered = "LIB_PJS",
#[doc = "[`ListBoxPopupJsonFiltered`]의 정의"]
ListBoxPopupJsonFilteredDef
},
{
#[doc = "여러 선택지를 선택할 수 있는 [`ListBox`]"]
ListBoxMultiple = "LIB_M",
#[doc = "[`ListBoxMultiple`]의 정의"]
ListBoxMultipleDef
},
{
#[doc = "하나의 선택지만 선택할 수 있는 [`ListBox`]"]
ListBoxSingle = "LIB_S",
#[doc = "[`ListBoxSingle`]의 정의"]
ListBoxSingleDef
}
];
#[derive(WdLsData)]
#[allow(unused)]
pub struct ListBoxLSData {
#[wd_lsdata(index = "0")]
width: Option<String>,
#[wd_lsdata(index = "1")]
visible_items: Option<i32>,
#[wd_lsdata(index = "2")]
height: Option<String>,
#[wd_lsdata(index = "3")]
invalid: Option<bool>,
#[wd_lsdata(index = "4")]
enabled: Option<bool>,
#[wd_lsdata(index = "5")]
readonly: Option<bool>,
#[wd_lsdata(index = "6")]
multiple_selection: Option<bool>,
#[wd_lsdata(index = "7")]
required: Option<bool>,
#[wd_lsdata(index = "8")]
is_popup: Option<bool>,
#[wd_lsdata(index = "9")]
icon_visibility: Option<String>,
#[wd_lsdata(index = "10")]
first_value_visibility: Option<String>,
#[wd_lsdata(index = "11")]
second_value_visibility: Option<String>,
#[wd_lsdata(index = "12")]
visibility: Option<String>,
#[wd_lsdata(index = "13")]
input_state: Option<String>,
#[wd_lsdata(index = "14")]
drag_source_info: Option<String>,
#[wd_lsdata(index = "15")]
drop_target_info: Option<String>,
#[wd_lsdata(index = "16")]
scroll_top: Option<i32>,
#[wd_lsdata(index = "17")]
access_key: Option<String>,
#[wd_lsdata(index = "18")]
available: Option<bool>,
#[wd_lsdata(index = "19")]
server_filter: Option<String>,
#[wd_lsdata(index = "20")]
complete: Option<bool>,
#[wd_lsdata(index = "21")]
filtered: Option<bool>,
#[wd_lsdata(index = "22")]
table_data_definition: Option<String>,
#[wd_lsdata(index = "23")]
item_table_data: Option<String>,
#[wd_lsdata(index = "24")]
history_table_data: Option<String>,
#[wd_lsdata(index = "25")]
custom_data: Option<String>,
#[wd_lsdata(index = "26")]
custom_style: Option<String>,
#[wd_lsdata(index = "27")]
table_data_item_design: Option<String>,
#[wd_lsdata(index = "28")]
labelled_by: Option<String>,
}
impl<'a> ListBox<'a> {
pub const fn new(id: Cow<'static, str>, element_ref: scraper::ElementRef<'a>) -> Self {
Self {
id,
element_ref,
lsdata: OnceCell::new(),
lsevents: OnceCell::new(),
items: OnceCell::new(),
}
}
pub fn items(&self) -> impl Iterator<Item = &ListBoxItemDefWrapper> {
self.items
.get_or_init(|| {
let items_selector = scraper::Selector::parse("[ct]").unwrap();
self.element_ref
.select(&items_selector)
.filter_map(|elem_ref| {
let element = ElementDefWrapper::from_ref(elem_ref).ok()?;
match element {
ElementDefWrapper::ListBoxItem(item) => {
Some(ListBoxItemDefWrapper::Item(item))
}
ElementDefWrapper::ListBoxActionItem(item) => {
Some(ListBoxItemDefWrapper::ActionItem(item))
}
_ => None,
}
})
.collect()
})
.iter()
}
pub fn item_infos(&self) -> Result<impl Iterator<Item = ListBoxItemInfo>, WebDynproError> {
let items_selector = scraper::Selector::parse("[ct]").unwrap();
let vec = self
.element_ref
.select(&items_selector)
.map(|elem_ref| ListBoxItemInfo::from_element_ref(elem_ref))
.collect::<Result<Vec<ListBoxItemInfo>, WebDynproError>>()?;
Ok(vec.into_iter())
}
}
pub mod item;