Skip to content

Commit 4d5e590

Browse files
committed
datasets.js
1 parent 10366ca commit 4d5e590

3 files changed

Lines changed: 303 additions & 5 deletions

File tree

content/assets/js/datasets.js

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
// ========================================
2+
// Generic Resource Browser
3+
// ========================================
4+
5+
// Configuration passed to init()
6+
let config = null;
7+
8+
// Index data (loaded via script tag)
9+
let indexData = null;
10+
let columns = [];
11+
let dictionaries = {};
12+
let resources = [];
13+
14+
// Column indices (set after loading)
15+
let colIdx = {};
16+
17+
// ========================================
18+
// Utility Functions
19+
// ========================================
20+
21+
// Parse URL parameters based on configured search params
22+
function getUrlParams() {
23+
const params = new URLSearchParams(window.location.search);
24+
const result = {};
25+
26+
for (const param of config.searchParams) {
27+
result[param] = params.get(param) || '';
28+
}
29+
30+
return result;
31+
}
32+
33+
// Check if any search params are present
34+
function hasSearchParams(params) {
35+
return config.searchParams.some(param => params[param]);
36+
}
37+
38+
// Escape HTML
39+
function escapeHtml(text) {
40+
if (!text) return '';
41+
const div = document.createElement('div');
42+
div.textContent = text;
43+
return div.innerHTML;
44+
}
45+
46+
// Get value from a resource row, resolving dictionary references
47+
function getValue(row, colName) {
48+
const idx = colIdx[colName];
49+
if (idx === undefined) return null;
50+
51+
const val = row[idx];
52+
if (val === null || val === undefined || val === -1) return null;
53+
54+
// Check if this column uses a dictionary
55+
const dict = dictionaries[colName];
56+
if (dict) {
57+
if (Array.isArray(val)) {
58+
return val.map(i => i >= 0 ? dict[i] : null).filter(v => v !== null);
59+
}
60+
return val >= 0 ? dict[val] : null;
61+
}
62+
63+
return val;
64+
}
65+
66+
// Get single string value (first if array)
67+
function getStringValue(row, colName) {
68+
const val = getValue(row, colName);
69+
if (Array.isArray(val)) return val[0] || '';
70+
return val || '';
71+
}
72+
73+
// Check if a value matches the search term (case-insensitive, starts-with)
74+
function matchesSearch(value, searchTerm) {
75+
if (!searchTerm) return true;
76+
if (!value) return false;
77+
78+
const lowerSearch = searchTerm.toLowerCase();
79+
80+
if (Array.isArray(value)) {
81+
return value.some(v => v && v.toLowerCase().includes(lowerSearch));
82+
}
83+
84+
return value.toLowerCase().includes(lowerSearch);
85+
}
86+
87+
// ========================================
88+
// Search Functions
89+
// ========================================
90+
91+
// Perform the search using the in-memory index
92+
function performSearch(params) {
93+
const startTime = performance.now();
94+
const resultsBody = document.getElementById('resultsBody');
95+
const totalCount = document.getElementById('totalCount');
96+
const searchTime = document.getElementById('searchTime');
97+
98+
if (!indexData) {
99+
resultsBody.innerHTML = '<div class="error">Index not loaded</div>';
100+
return;
101+
}
102+
103+
// Filter resources based on all configured search params
104+
const matches = resources.filter(row => {
105+
for (const param of config.searchParams) {
106+
if (params[param] && !matchesSearch(getValue(row, param), params[param])) {
107+
return false;
108+
}
109+
}
110+
return true;
111+
});
112+
113+
const elapsed = (performance.now() - startTime).toFixed(1);
114+
115+
totalCount.textContent = `(${matches.length} found)`;
116+
searchTime.textContent = `${elapsed}ms`;
117+
118+
if (matches.length === 0) {
119+
resultsBody.innerHTML = '<div class="empty-state">No content found matching your criteria</div>';
120+
return;
121+
}
122+
123+
// Render results table
124+
const displayResults = matches.slice(0, config.maxResults);
125+
126+
// Build table header from configured columns
127+
let html = `
128+
<table class="grid">
129+
<thead>
130+
<tr>
131+
${config.displayColumns.map(col => `<th>${escapeHtml(col.label)}</th>`).join('')}
132+
</tr>
133+
</thead>
134+
<tbody>
135+
`;
136+
137+
// Build table rows
138+
for (const row of displayResults) {
139+
html += '<tr>';
140+
141+
for (const col of config.displayColumns) {
142+
const value = getStringValue(row, col.key);
143+
144+
if (col.isLink && value) {
145+
const id = getStringValue(row, 'id');
146+
html += `<td><a href="${config.detailPage}?id=${encodeURIComponent(id)}">${escapeHtml(value)}</a></td>`;
147+
} else {
148+
html += `<td>${escapeHtml(value)}</td>`;
149+
}
150+
}
151+
152+
html += '</tr>';
153+
}
154+
155+
html += '</tbody></table>';
156+
157+
if (matches.length > config.maxResults) {
158+
html += `<div class="empty-state">Showing first ${config.maxResults} of ${matches.length} results</div>`;
159+
}
160+
161+
resultsBody.innerHTML = html;
162+
}
163+
164+
// ========================================
165+
// Index Loading
166+
// ========================================
167+
168+
// Initialize the index from the global variable
169+
function loadIndex() {
170+
try {
171+
if (typeof INDEX === 'undefined') {
172+
throw new Error('Index not found. Make sure the index JS file is in the same folder.');
173+
}
174+
175+
indexData = INDEX;
176+
columns = indexData.columns || [];
177+
dictionaries = indexData.dictionaries || {};
178+
resources = indexData.resources || [];
179+
180+
// Build column index map
181+
columns.forEach((col, idx) => {
182+
colIdx[col] = idx;
183+
});
184+
185+
return true;
186+
187+
} catch (error) {
188+
console.error('Error loading index:', error);
189+
return false;
190+
}
191+
}
192+
193+
// ========================================
194+
// Search Page Initialization
195+
// ========================================
196+
197+
function init(cfg) {
198+
config = cfg;
199+
const params = getUrlParams();
200+
201+
// Populate form fields from URL params
202+
for (const param of config.searchParams) {
203+
const element = document.getElementById(param);
204+
if (element) {
205+
element.value = params[param];
206+
}
207+
}
208+
209+
// Load index from global variable
210+
const loaded = loadIndex();
211+
212+
// If we have search params and index loaded, perform the search
213+
if (loaded && hasSearchParams(params)) {
214+
performSearch(params);
215+
}
216+
}
217+
218+
// ========================================
219+
// Detail Page Functions
220+
// ========================================
221+
222+
// Get id from URL
223+
function getIdParam() {
224+
const params = new URLSearchParams(window.location.search);
225+
return params.get('id') || '';
226+
}
227+
228+
// Toggle JSON section visibility
229+
function toggleJson() {
230+
const content = document.getElementById('jsonContent');
231+
const toggle = document.getElementById('jsonToggle');
232+
233+
if (content.classList.contains('open')) {
234+
content.classList.remove('open');
235+
toggle.textContent = 'Show';
236+
} else {
237+
content.classList.add('open');
238+
toggle.textContent = 'Hide';
239+
}
240+
}
241+
242+
// Render resource details
243+
function renderResource(res) {
244+
const narrativeHtml = res.text?.div || '<p>No narrative available</p>';
245+
246+
return `
247+
<div class="narrative">
248+
${narrativeHtml}
249+
</div>
250+
251+
<div class="json-section">
252+
<div class="json-header" onclick="toggleJson()">
253+
<span class="json-title">Raw JSON</span>
254+
<span class="json-toggle" id="jsonToggle">Show</span>
255+
</div>
256+
<div class="json-content" id="jsonContent">
257+
<pre class="raw-json">${escapeHtml(JSON.stringify(res, null, 2))}</pre>
258+
</div>
259+
</div>
260+
`;
261+
}
262+
263+
// Load and display the resource
264+
function loadResource() {
265+
const content = document.getElementById('content');
266+
const id = getIdParam();
267+
268+
if (!id) {
269+
content.innerHTML = '<div class="empty-state">No resource ID specified</div>';
270+
return;
271+
}
272+
273+
if (typeof DATA_SET === 'undefined') {
274+
content.innerHTML = '<div class="error">Data not found. Make sure the data JS file is in the same folder.</div>';
275+
return;
276+
}
277+
278+
const res = DATA_SET[id];
279+
280+
if (!res) {
281+
content.innerHTML = `<div class="empty-state">Resource "${escapeHtml(id)}" not found</div>`;
282+
return;
283+
}
284+
285+
content.innerHTML = renderResource(res);
286+
287+
// Update page title
288+
const name = res.code?.coding?.[0]?.display || res.name || id;
289+
document.title = `${name} - ${res.resourceType || 'Resource'} Details`;
290+
}

package-list.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,15 @@
1818
"status" : "release",
1919
"sequence" : "Publications",
2020
"current" : true,
21-
"date" : "2025-06-03"
21+
"date" : "2025-07-06"
22+
},
23+
{
24+
"version" : "0.0.1",
25+
"desc" : "First release",
26+
"path" : "http://fhir.org/templates/hl7.au.base.template/0.1.0",
27+
"status" : "release",
28+
"sequence" : "Publications",
29+
"date" : "2020-07-03"
2230
}
2331
]
2432
}

package/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)