1// File: docs/assets/js/ui_component.js 2 3;(function() { 4 console.log("ð¡ ui_component.js loaded"); 5 6 // Registry of widget-mounting functions 7 var registry = { 8 select: mountSelect 9 }; 10 11 document.addEventListener("DOMContentLoaded", function() { 12 document.querySelectorAll("[data-ui-component]").forEach(function(el) { 13 var cfg; 14 try { 15 cfg = JSON.parse(el.getAttribute("data-ui-component")); 16 } catch (err) { 17 console.error( 18 "[ui_component] JSON parse failed:", 19 err, 20 el.getAttribute("data-ui-component") 21 ); 22 return; 23 } 24 var mount = registry[cfg.type]; 25 if (!mount) { 26 console.error("[ui_component] unknown widget type:", cfg.type); 27 return; 28 } 29 mount(el, cfg); 30 }); 31 }); 32 33 function mountSelect(container, cfg) { 34 console.log("[ui_component:" + cfg.id + "] mounting select"); 35 36 // 1) build label + <select> 37 var label = document.createElement("label"); 38 label.htmlFor = cfg.id; 39 label.textContent = cfg.label || ""; 40 // Apply Material theme label class 41 label.classList.add("md-label"); 42 // Ensure consistent font-size and spacing 43 label.classList.add("md-body-1"); 44 label.style.marginRight = "0.5rem"; 45 46 var select = document.createElement("select"); 47 select.id = cfg.id; 48 // Apply Material theme input/select class 49 select.classList.add("md-input"); 50 // Ensure consistent font-size and spacing 51 select.classList.add("md-body-1"); 52 select.style.marginLeft = "0.5rem"; 53 // Match font-size to label 54 select.style.fontSize = window.getComputedStyle(label).fontSize; 55 56 // 2) swap out placeholder <div> for our real UI 57 var wrapper = document.createElement("div"); 58 // Apply Material theme form-field wrapper class 59 wrapper.classList.add("md-form-field"); 60 // Add vertical spacing below each label/select pair 61 wrapper.style.marginBottom = "1rem"; 62 wrapper.appendChild(label); 63 wrapper.appendChild(select); 64 container.replaceWith(wrapper); 65 66 // 3) derive the root state key (e.g. "accessKeys" or "selectedKey") 67 var dataKey = cfg.options.split(/[^A-Za-z0-9_]/)[0]; 68 69 // 4) build an options-getter that bails if the root value is null/undefined 70 // and uses StateStore.get(...) instead of reading S.state directly. 71 var optSrc = 72 "try {\n" + 73 " var root = S.get('" + dataKey + "');\n" + 74 " if (!root) return [];\n" + 75 " with ({ " + dataKey + ": root }) { return (" + cfg.options + ") || []; }\n" + 76 "} catch (e) {\n" + 77 " console.warn('[ui_component] options failed:', " + 78 JSON.stringify(cfg.options) + ", e);\n" + 79 " return [];\n" + 80 "}"; 81 var getOptions = new Function("S", optSrc); 82 83 // 5) value and label extractors 84 var getVal = cfg.valueField === "this" 85 ? function(item) { return item; } 86 : new Function("item", "return item." + cfg.valueField + ";"); 87 88 var getLab = new Function("item", 89 "try { with(item) { return " + cfg.labelExpr + "; } } " + 90 "catch(e) { console.warn('[ui_component] labelExpr failed:', " + 91 JSON.stringify(cfg.labelExpr) + ", e); return ''; }" 92 ); 93 94 // 6) render function with auto-default 95 function render() { 96 try { 97 var opts = getOptions(window.StateStore); 98 console.debug( 99 "[ui_component:" + cfg.id + "] rendering " + opts.length + " options" 100 ); 101 select.innerHTML = opts.map(function(o) { 102 var v = getVal(o); 103 var l = getLab(o); 104 return '<option value="' + v + '">' + l + '</option>'; 105 }).join(""); 106 107 var cur = window.StateStore.get(cfg.selected); 108 109 // If nothing selected yet (null OR empty string), default to first option 110 if ((cur == null || cur === "") && opts.length > 0) { 111 console.debug("[ui_component:" + cfg.id + "] defaulting to first option"); 112 window.StateStore.set(cfg.onChange, opts[0]); 113 } 114 // Otherwise reflect the current selection 115 else if (cur != null && cur !== "") {
116 select.value = String(getVal(cur)); 117 } 118 } catch (err) { 119 console.error("[ui_component:" + cfg.id + "] render error:", err); 120 } 121 } 122 123 // 7) initial render + subscriptions 124 render(); 125 window.StateStore.subscribe(dataKey, render); 126 window.StateStore.subscribe(cfg.selected, render); 127 128 // 8) wire up user changes 129 select.addEventListener("change", function() { 130 try { 131 var opts = getOptions(window.StateStore); 132 var picked = opts.find(function(o) { 133 return String(getVal(o)) === select.value; 134 }); 135 console.debug("[ui_component:" + cfg.id + "] user selected:", picked); 136 window.StateStore.set(cfg.onChange, picked); 137 } catch (err) { 138 console.error("[ui_component:" + cfg.id + "] onChange error:", err); 139 } 140 }); 141 } 142})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.