1 2// Polyfill for html_entities in case base-2019-06-24.js is not loaded on the page. 3// Encodes &, <, >, ", and ' to their HTML entity equivalents. 4if (typeof html_entities !== 'function') { 5 window.html_entities = function(text) { 6 if (text == null) return ''; 7 return String(text) 8 .replace(/&/g, '&') 9 .replace(/</g, '<') 10 .replace(/>/g, '>') 11 .replace(/"/g, '"') 12 .replace(/'/g, '''); 13 }; 14} 15 16export class WebComponent extends HTMLElement { 17 shadowRoot; // Shadow DOM 18 placeHolderRoot; // Placeholder root 19 state = {}; // State object 20 21 // Per-class caches for the shadow-DOM caching helpers below. Keyed by class reference 22 // (WeakMap) so unrelated components (Button, BasicCheckbox, BasicField, ...) each get 23 // their own independent cache entry with no collisions. 24 static _templateCache = new WeakMap(); // ClassRef -> HTMLTemplateElement 25 static _styleSheetCache = new WeakMap(); // ClassRef -> CSSStyleSheet 26 static _supportsAdoptedStyleSheetsResult = null; 27 28 // Feature-detect once and memoize: constructed stylesheets (needed for adoptedStyleSheets) 29 // aren't available on every browser, so callers need a way to know whether to fall back 30 // to a plain per-instance <style> tag instead. 31 static supportsAdoptedStyleSheets() { 32 if (WebComponent._supportsAdoptedStyleSheetsResult === null) { 33 WebComponent._supportsAdoptedStyleSheetsResult = typeof CSSStyleSheet !== 'undefined' 34 && 'replaceSync' in CSSStyleSheet.prototype 35 && typeof ShadowRoot !== 'undefined' 36 && 'adoptedStyleSheets' in ShadowRoot.prototype; 37 } 38 return WebComponent._supportsAdoptedStyleSheetsResult; 39 } 40 41 // Building shadow DOM markup via a fresh innerHTML string on every instance forces the 42 // browser to re-parse that HTML on every single instantiation. With many instances of 43 // the same component on one page (e.g. dozens of cards each containing several buttons, 44 // checkboxes, and fields), that parsing cost is paid over and over for identical markup. 45 // This parses `html` into a <template> once per calling class and returns a fresh clone 46 // of its content on every call after that â cloning is far cheaper than re-parsing. 47 static getTemplateClone(ClassRef, html) { 48 let template = WebComponent._templateCache.get(ClassRef); 49 if (!template) { 50 template = document.createElement('template'); 51 template.innerHTML = html; 52 WebComponent._templateCache.set(ClassRef, template); 53 } 54 return template.content.cloneNode(true); 55 } 56 57 // Same idea for CSS: parses `cssText` into a CSSStyleSheet once per calling class via 58 // replaceSync(), so it can be shared across every instance via shadowRoot.adoptedStyleSheets 59 // instead of re-parsing the same CSS text into a fresh <style> tag per instance. Returns 60 // null when constructed stylesheets aren't supported, so the caller can fall back to a 61 // plain <style> tag with the same cssText. 62 // 63 // Note: cssText must not contain @import â @import rules are silently dropped by 64 // replaceSync() per spec. Load external stylesheets (e.g. icon fonts) via a <link> in the 65 // template markup instead. 66 static getComponentStyleSheet(ClassRef, cssText) { 67 if (!WebComponent.supportsAdoptedStyleSheets()) return null; 68 let sheet = WebComponent._styleSheetCache.get(ClassRef); 69 if (!sheet) { 70 sheet = new CSSStyleSheet(); 71 sheet.replaceSync(cssText); 72 WebComponent._styleSheetCache.set(ClassRef, sheet); 73 } 74 return sheet; 75 } 76 77 constructor() { 78 super(); 79 this.placeHolderRoot = document.createElement('div'); 80 this.shadowRoot = this.attachShadow({ mode: 'open' }); 81 } 82 83 // Updates state and triggers re-render 84 setState(newState) { 85 this.state = { ...this.state, ...newState }; 86 this.render(); 87 } 88 89 // Lifecycle hook to trigger the initial render 90 connectedCallback() { 91 this.render(); 92 } 93 94 // Placeholder render method 95 render() { 96 // Should be implemented in derived components 97 throw new Error('Render method must be implemented in the derived component.'); 98 } 99 100 setEventListeners() { 101 // Optional to be implemented in derived components 102 } 103 104 loadBaseCSS() { 105 const base2017 = document.createElement('link'); 106 base2017.setAttribute('rel', 'stylesheet'); 107 base2017.setAttribute('type', 'text/css'); 108 base2017.setAttribute('href', '/css/base-2017-05-15.css'); 109 this.placeHolderRoot.appendChild(base2017); 110 const base2018 = document.createElement('link'); 111 base2018.setAttribute('rel', 'stylesheet'); 112 base2018.setAttribute('type', 'text/css'); 113 base2018.setAttribute('href', '/css/base-2018-11-17.css'); 114 this.placeHolderRoot.appendChild(base2018); 115 } 116 117 // TODO: more discussion needed on how to handle jQuery depen
117dency for components. For now, we can load it globally in the head if not already present. 118 loadJQuery() { 119 // jQuery is a global dependency. Load it once in document head to avoid 120 // reinitializing window.$ / window.jQuery when multiple components mount. 121 if (window.jQuery && window.jQuery.fn) return; 122 if (document.querySelector('script[data-webcomponent-lib="jquery"]')) return; 123 if (document.querySelector('script[src*="/js/jquery.min.js"]')) return; 124 125 const jQuery = document.createElement('script'); 126 jQuery.setAttribute('src', '/js/jquery.min.js'); 127 jQuery.setAttribute('data-webcomponent-lib', 'jquery'); 128 document.head.appendChild(jQuery); 129 } 130 131 loadBaseJS() { 132 // Base JS is also global; load once in head to avoid duplicate execution. 133 if (document.querySelector('script[data-webcomponent-lib="base-js"]')) return; 134 if (document.querySelector('script[src*="/js/base-2019-06-24.js"]')) return; 135 136 const baseJS = document.createElement('script'); 137 baseJS.setAttribute('src', '/js/base-2019-06-24.js'); 138 baseJS.setAttribute('data-webcomponent-lib', 'base-js'); 139 document.head.appendChild(baseJS); 140 } 141 142 loadChartJS() { 143 return new Promise((resolve) => { 144 if (typeof Chart !== 'undefined') { resolve(); return; } 145 const existing = document.querySelector('script[src="/js/chart4.min.js"]'); 146 if (existing) { existing.addEventListener('load', resolve); return; } 147 const script = document.createElement('script'); 148 script.src = '/js/chart4.min.js'; 149 script.onload = resolve; 150 document.head.appendChild(script); 151 }); 152 } 153 154 loadD3() { 155 const loadScript = (src, check) => new Promise(resolve => { 156 if (check()) { resolve(); return; } 157 const existing = document.querySelector(`script[src="${src}"]`); 158 if (existing) { existing.addEventListener('load', resolve); return; } 159 const script = document.createElement('script'); 160 script.src = src; script.onload = resolve; 161 document.head.appendChild(script); 162 }); 163 return Promise.all([ 164 loadScript('https://d3js.org/d3.v7.min.js', 165 () => typeof d3 !== 'undefined'), 166 loadScript('https://cdn.jsdelivr.net/npm/topojson-client@3/dist/topojson-client.min.js', 167 () => typeof topojson !== 'undefined'), 168 ]); 169 } 170 171 loadIcons() { 172 const icons = ['https://unicons.iconscout.com/release/v4.0.8/css/line.css', 'https://unicons.iconscout.com/release/v4.0.8/css/solid.css', 'https://unicons.iconscout.com/release/v4.0.8/css/thinline.css']; 173 return Promise.all( 174 icons.map((file) => { 175 return new Promise((resolve) => { 176 const link = document.createElement("link"); 177 link.rel = "stylesheet"; 178 link.href = file; 179 link.onload = () => resolve(); // Wait for CSS to load 180 this.placeHolderRoot.appendChild(link); 181 }); 182 }) 183 ); 184 185 } 186 187 loadTemplate(template) { 188 const templateElement = document.createElement('template'); 189 templateElement.innerHTML += this.placeHolderRoot.innerHTML; 190 templateElement.innerHTML += template; 191 this.shadowRoot.innerHTML = ''; 192 this.shadowRoot.appendChild(templateElement.content.cloneNode(true)); 193 194 this.setEventListeners(); 195 } 196 197} 198
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.