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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//!
//! Helper trait for managing options struct which extends [Object](js_sys::Object)
//! ```ignore
//! # use wasm_bindgen::JsValue;
//! # use crate::workflow_wasm::options::OptionsTrait;
//! // create MyOptions struct
//!
//! #[wasm_bindgen]
//! extern "C" {
//!     #[derive(Debug, Clone, PartialEq, Eq)]
//!     #[wasm_bindgen(extends = js_sys::Object)]
//!     pub type MyOptions;
//! }
//!
//!
//! impl OptionsTrait for MyOptions{}
//!
//! //impl methods as you need
//! impl MyOptions{
//!     /// Set title
//!     pub fn title(self, title:&str)->Self{
//!         self.set("title", JsValue::from(title))
//!     }
//!
//!     /// Set active
//!     pub fn active(self, active:bool)->Self{
//!         self.set("active", JsValue::from(active))
//!     }
//! }
//!
//! // use MyOptions
//!
//! let options = MyOptions::new()
//!     .title("title text")
//!     .active(true);
//!
//! ```
//!

use js_sys::Object;
use wasm_bindgen::prelude::*;

pub trait OptionsTrait {
    /// "Construct a new `Options`.
    ///
    fn new() -> Self
    where
        Self: wasm_bindgen::JsCast,
    {
        #[allow(unused_mut)]
        let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(Object::new());
        ret = ret.initialize();
        ret
    }

    fn initialize(self) -> Self
    where
        Self: wasm_bindgen::JsCast,
    {
        self
    }

    fn set(self, mut key: &str, value: JsValue) -> Self
    where
        Self: wasm_bindgen::JsCast,
    {
        let mut target = self.as_ref().clone();

        if key.contains('.') {
            let mut name_parts: Vec<&str> = key.split('.').collect();
            key = name_parts.pop().unwrap();

            for name in name_parts {
                //log_info!("name: {}, target: {:?}", name, target);
                let r = ::js_sys::Reflect::get(&target, &JsValue::from(name));

                match r {
                    Ok(r) => {
                        if !r.is_undefined() {
                            target = r
                        } else {
                            let object = Object::new();
                            let new_target = JsValue::from(object);
                            //log_info!("new_target: {:?}", new_target);
                            let _ =
                                ::js_sys::Reflect::set(&target, &JsValue::from(name), &new_target);

                            target = new_target;
                        }
                    }
                    Err(err) => {
                        panic!("OptionsExt::set(): unable to find property `{name}`, err: {err:?}");
                    }
                }
            }

            //log_info!("final: key: {}, target: {:?}", key, target);
        }

        let r = ::js_sys::Reflect::set(&target, &JsValue::from(key), &value);
        debug_assert!(
            r.is_ok(),
            "setting properties should never fail on our dictionary objects"
        );
        let _ = r;

        self
    }
}

/*
#[cfg(test)]
mod test{
    use super::*;
    use crate as workflow_wasm;
    #[test]
    fn test(){
        #[wasm_bindgen]
        extern "C" {
            #[wasm_bindgen(extends = js_sys::Object)]
            #[derive(Debug, Clone, PartialEq, Eq)]
            pub type MyOptions;
        }

        impl workflow_wasm::options::OptionsTrait for MyOptions{}

        //impl methods as you need
        impl MyOptions{
            /// Set title
            pub fn title(self, title:&str)->Self{
                self.set("title", JsValue::from(title))
            }

            /// Set active
            pub fn active(self, active:bool)->Self{
                self.set("active", JsValue::from(active))
            }
        }

        // use MyOptions

        let options = MyOptions::new()
            .title("title text")
            .active(true);
    }
}

*/