/** Get value of the DOM element. Returns value for elements recognized by get_ctl_type() function. * \param[in] el \b const dom::element&, The element. * \return \b value_t, value of the element. **/ inline value_t get_value(dom::element& el ) { switch(get_ctl_type(el)) { case CTL_EDIT: case CTL_DECIMAL: case CTL_CURRENCY: case CTL_PASSWORD: case CTL_NUMERIC: case CTL_PROGRESS: case CTL_SLIDER: case CTL_SELECT_SINGLE: case CTL_SELECT_MULTIPLE: case CTL_DD_SELECT: case CTL_TEXTAREA: case CTL_DATE: case CTL_CALENDAR: default: return el.get_value(); // special cases: case CTL_UNKNOWN: if( !aux::wcseq(el.get_attribute("type"),L"hidden")) break; //else fall below if it is hidden case CTL_BUTTON: return value_t(el.get_attribute("value")); case CTL_CHECKBOX: return get_checkbox_bits(el); case CTL_RADIO: return get_radio_index(el); case CTL_HTMLAREA: return value_t(el.get_html(false/*inner*/)); } return value_t(); }
NODE_STATE get_state(dom::element item) { if(wcseq(item.get_attribute("check"),L"on")) return NODE_ON; else if(wcseq(item.get_attribute("check"),L"mixed")) return NODE_MIXED; else return NODE_OFF; }
void notify(dom::element& el, NMHL_HYPERLINK::type code) { // send notification NMHL_HYPERLINK nm; memset(&nm,0,sizeof(nm)); HWND hwnd = el.get_element_hwnd(true); nm.hdr.code = HLN_HYPERLINK; nm.hdr.hwndFrom = hwnd; nm.hdr.idFrom = GetDlgCtrlID(hwnd); nm.action = code; nm.he = el; dom::element root = el.root(); const wchar_t *pHREF = el.get_attribute("href"); if(pHREF) { if(code == NMHL_HYPERLINK::CLICK && pHREF[0] == '#') // anchor name, this is a local hyperlink { if( pHREF+1 == 0 ) // href='#' case return; dom::element anchor_el = root.find_first("[id='%S'],[name='%S']",pHREF+1,pHREF+1); //find_element_by_name(el.root_element(hwnd), pHREF + 1); if(anchor_el.is_valid()) // found { anchor_el.scroll_to_view(true /* scroll it to top of the view */); return; // shall host be notified about this? } } wcsncpy(nm.szHREF,pHREF,MAX_URL_LENGTH); el.combine_url(nm.szHREF,MAX_URL_LENGTH); } const wchar_t *pszTarget = el.get_attribute("target"); if(pszTarget) { if(code == NMHL_HYPERLINK::CLICK && try_to_load( root, nm.szHREF, pszTarget )) return; wcsncpy(nm.szTarget,pszTarget,MAX_URL_LENGTH); } ::SendMessage(hwnd,WM_BEHAVIOR_NOTIFY,HLN_HYPERLINK,LPARAM(&nm)); }
void setup_node( dom::element node ) { dom::element on = node.find_first("option[check='on'],options[check='on']"); dom::element off = node.find_first("option[check='off'],options[check='off']"); dom::element mixed = node.find_first("options[check='mixed']"); const wchar_t* _prev = node.get_attribute(CHECK_ATTR); std::wstring prev = _prev?_prev:L""; if( mixed.is_valid() || (on.is_valid() && off.is_valid())) { node.set_attribute(CHECK_ATTR,L"mixed"); if(prev != L"mixed") node.update(); } else if( on.is_valid() ) { node.set_attribute(CHECK_ATTR,L"on"); if(prev != L"on") node.update(); } else { node.set_attribute(CHECK_ATTR,L"off"); if(prev != L"off") node.update(); } }
bool is_fullpath(dom::element &el) { BOOL bFullPath = (el.get_attribute("fullpath") != NULL); if ( !bFullPath ) { dom::element ep = el.parent(); bFullPath = (ep.is_valid())?(ep.get_attribute("fullpath") != NULL):FALSE; } return bFullPath; }
// returns bit mask - checkboxes set inline value_t get_checkbox_bits(dom::element& el ) { selected_cb selected; dom::element r = el.parent(); // ATTN: I assume here that all checkboxes in the group belong to the same parent! r.find_all(&selected, "[type='checkbox'][name='%S']", el.get_attribute("name")); int m = 1, v = 0; for( unsigned int n = 0; n < selected.elements.size(); ++n, m <<= 1 ) if ( selected.elements[n].get_state(STATE_CHECKED) ) v |= m; return selected.elements.size()==1?value_t(v==1):value_t(v); // for alone checkbox we return true/false }
inline value_t get_radio_index( dom::element& el ) { selected_cb selected; dom::element r = el.parent(); // ATTN: I assume here that all radios in the group belong to the same parent! r.find_all(&selected, "[type='radio'][name='%S']", el.get_attribute("name")); for( unsigned int n = 0; n < selected.elements.size(); ++n ) if ( selected.elements[n].get_state(STATE_CHECKED) ) return value_t(int(n)); return value_t(); }
// select bool select_tab( dom::element& tabs_el, dom::element& tab_el ) { if(tab_el.get_state(STATE_CURRENT)) // already selected, nothing to do... return true; // but we've handled it. //find currently selected element (tab and panel) and remove "selected" from them dom::element prev_panel_el = tabs_el.find_first(":root>[name]:expanded"); dom::element prev_tab_el = tabs_el.find_first(":root>.strip>[panel]:current"); // find new tab and panel const wchar_t* pname = tab_el.get_attribute("panel"); dom::element panel_el = tabs_el.find_first(":root>[name=\"%S\"]", pname); if( !panel_el.is_valid() || !tab_el.is_valid() ) { assert(false); // panel="somename" without matching name="somename" return true; } if( prev_panel_el.is_valid() ) { prev_panel_el.set_attribute("selected", 0); // remove selected attribute - just in case somone is using attribute selectors prev_panel_el.set_state(STATE_COLLAPSED,0); // set collapsed in case of someone use it for styling } if( prev_tab_el.is_valid() ) { prev_tab_el.set_attribute("selected", 0); // remove selected attribute prev_tab_el.set_state(0,STATE_CURRENT); // reset also state flag, :current } panel_el.set_attribute("selected", L""); // set selected attribute (empty) panel_el.set_state(STATE_EXPANDED,0); // expand it tab_el.set_attribute("selected", L""); // set selected attribute (empty) tab_el.set_state(STATE_CURRENT,0); // set also state flag, :current // notify all parties involved if (prev_tab_el.is_valid()) { prev_tab_el.post_event(ELEMENT_COLLAPSED,0, prev_tab_el); // source here is old collapsed tab itself } tab_el.post_event(ELEMENT_EXPANDED,0, tab_el); // source here is new expanded tab itself // NOTE #1: these event will bubble from panel elements up to the root so panel itself, tabs ctl, its parent, etc. // will receive these notifications. Handle them if you need to change UI dependent from current tab. // NOTE #2: while handling this event in: // virtual BOOL on_event (HELEMENT he, HELEMENT target, BEHAVIOR_EVENTS type, UINT reason ), // HELEMENT target is the panel element being collapsed/expanded return true; }
// sets checkboxes by bit mask inline void set_checkbox_bits(dom::element& el, const value_t& t ) { selected_cb selected; dom::element r = el.parent(); // ATTN: I assume here that all checkboxes in the group belong to the same parent! r.find_all(&selected, "[type='checkbox'][name='%S']", el.get_attribute("name")); int m = 1, v = selected.elements.size()==1?(t.get(false)?1:0):t.get(0); for( unsigned int n = 0; n < selected.elements.size(); ++n, m <<= 1 ) { dom::element& e = selected.elements[n]; if( (v & m) != 0) e.set_state( STATE_CHECKED, 0 ) ; else e.set_state( 0, STATE_CHECKED ) ; } }
inline void set_radio_index( dom::element& el, const value_t& t ) { selected_cb selected; dom::element r = el.parent(); // ATTN: I assume here that all radios in the group belong to the same parent! r.find_all(&selected, "[type='radio'][name='%S']", el.get_attribute("name")); unsigned int idx = (unsigned int)t.get(0); for( unsigned int n = 0; n < selected.elements.size(); ++n ) { dom::element& e = selected.elements[n]; if ( n == idx) e.set_state(STATE_CHECKED, 0); else e.set_state(0, STATE_CHECKED); } }
inline void set_radio_index( dom::element& el, const json::value& t ) { selected_cb selected; dom::element r = el.parent(); // ATTN: I assume here that all radios in the group belong to the same parent! r.find_all(&selected, "[type='radio'][name='%S']", el.get_attribute("name")); unsigned int idx = (unsigned int)t.get(0); for( unsigned int n = 0; n < selected.elements.size(); ++n ) { dom::element& e = selected.elements[n]; if ( n == idx) { e.set_value(json::value(true)); break; } } }
/** is it multiple selectable? **/ bool is_multiple (const dom::element& table) { return table.get_attribute ("multiple") != 0; }
//toggles checkmark void toggle_checkmark( dom::element select, dom::element item ) { const wchar_t* _old_state = item.get_attribute(CHECK_ATTR); std::wstring old_state = _old_state?_old_state:L""; if( streq(item.get_element_type(), "options")) { // non-terminal node case // inner function struct child_toggler: dom::callback { const wchar_t* state; child_toggler(const wchar_t* st): state(st) {} inline bool on_element(HELEMENT he) { htmlayout::dom::element item = he; item.set_attribute( CHECK_ATTR, state ); return false; } }; const wchar_t* new_state; NODE_STATE old_state = get_state(item); if(old_state == NODE_OFF) new_state = L"on"; else new_state = L"off"; child_toggler ct( new_state ); // do it for all children item.find_all(&ct, "option,options"); // and for itself item.set_attribute( CHECK_ATTR, new_state ); item.update(); } else if( streq(item.get_element_type(), "option")) { // terminal node const wchar_t* new_state; if(wcseq(item.get_attribute("check"),L"on")) { new_state = L"off"; } else new_state = L"on"; item.set_attribute( CHECK_ATTR, new_state ); item.update(); } // as state was changed we need to update parent chain here too. dom::element p = item.parent(); while( p.is_valid() && p != select ) { if( streq(p.get_element_type(),"options" )) { setup_node(p); } p = p.parent(); } }
inline value_t get_option_value(const dom::element& opt ) { const wchar_t* val = opt.get_attribute("value"); if( val ) return value_t::from_string(val); return value_t(opt.text()); }
inline json::value get_option_value(const dom::element& opt ) { const wchar_t* val = opt.get_attribute("value"); if( val ) return json::value::from_string(val); return json::value(opt.text().c_str()); }