.Stat Data Explorer — demo 1 SDMX - Version OCDE
by jlujan
HTML
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>.Stat Data Explorer — demo SDMX</title>
</head>
<body>
<div class="header">
<div class="logo">SIS-CC</div>
<div class="nav-icons">
🔍 ⚙️ ❓
<span class="lang-switch" id="langSwitch" role="group" aria-label="Idioma">
<button type="button" data-lang="en">EN</button>
<button type="button" data-lang="es">ES</button>
<button type="button" data-lang="fr">FR</button>
</span>
</div>
</div>
<h1>.Stat Data Explorer</h1>
<p>Find, access and share data...</p>
<div class="search-container">
<input type="text" placeholder="Search by keywords" id="searchInput">
<button type="button">➡️</button>
</div>
<div class="browse-container">
<p>or browse by</p>
<div class="dropdown-container" id="dropdown">
<button class="category-dropdown" id="topicsBtn" aria-haspopup="true" aria-expanded="false">
TOPICOS ⬇️
</button>
<div class="submenu" id="submenu" role="dialog" aria-modal="true" aria-labelledby="submenuTitle">
<div class="submenu-header">
<h2 id="submenuTitle">Topics</h2>
<span class="submenu-meta" id="submenuMeta"></span>
<button type="button" class="submenu-close" id="submenuClose" aria-label="Cerrar">✕</button>
</div>
<div class="submenu-container" id="submenuBody"></div>
</div>
</div>
<div class="overlay" id="overlay"></div>
</div>
<div class="selection" id="selection" hidden></div>
<!-- ------------------------------------------------------------------
Fuente de datos: CategoryScheme SDMX 2.1.
Para leerlo de un archivo externo, poner XML_URL = 'CS_TOPICS.xml'
(requiere servidor por CORS; con file:// se usa el XML embebido).
------------------------------------------------------------------- -->
<script type="application/xml"...
CSS
* { box-sizing: border-box; }
body {
font-family: Arial, Helvetica, sans-serif;
margin: 0;
min-height: 100vh;
color: #222;
text-align: center;
background: #ff0099;
}
.header {
background: rgba(245,245,245,.96);
padding: 10px 18px;
display: flex;
justify-content: space-between;
align-items: center;
color: #222;
box-shadow: 0 1px 5px rgba(0,0,0,.16);
}
.logo { font-weight: 700; letter-spacing: 1px; }
.nav-icons { display: flex; align-items: center; gap: 10px; }
.lang-switch { display: inline-flex; gap: 4px; }
.lang-switch button {
border: 1px solid #bbb;
background: #fff;
color: #222;
padding: 4px 8px;
font-size: 12px;
cursor: pointer;
border-radius: 3px;
}
.lang-switch button[aria-pressed="true"] {
background: #0b5db3;
border-color: #0b5db3;
color: #fff;
}
h1 { margin: 28px 0 8px; color: #fff; font-size: 31px; }
body > p { color: #fff; margin-top: 0; }
.search-container {
background: #0d5cad;
padding: 10px;
display: inline-flex;
border-radius: 5px;
box-shadow: 0 6px 18px rgba(0,0,0,.18);
}
.search-container input {
padding: 9px 10px;
width: min(420px, 68vw);
border: 0;
outline: none;
}
.search-container button {
border: 0;
background: white;
padding: 0 12px;
cursor: pointer;
}
.browse-container { margin-top: 12px; }
.browse-container > p { color: #fff; margin-bottom: 4px; }
.dropdown-container { display: inline-block; position: relative; }
.category-dropdown {
min-width: 210px;
padding: 11px 14px;
border: none;
cursor: pointer;
font-size: 14px;
background: #f8a526;
color: #171717;
border-radius: 3px;
box-shadow: 0 3px 9px rgba(0,0,0,.22);
}
.overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(2, 15, 31, .52);
...
JavaScript
(function () {
"use strict";
var XML_URL = null; // p.ej. 'CS_TOPICS.xml' para cargar por fetch
var SCHEME_ID = 'CS_TOPICS'; // CategoryScheme a renderizar
var XML_LANG = 'http://www.w3.org/XML/1998/namespace';
var LANG_FALLBACK = ['en'];
var lang = 'es';
var scheme = null; // {id, urn, names, categories:[...]}
/* ---------- helpers DOM/XML (sin depender del prefijo str:/com:) ---------- */
function kids(el, local) {
var out = [], n = el.firstElementChild;
while (n) { if (n.localName === local) out.push(n); n = n.nextElementSibling; }
return out;
}
function firstKid(el, local) { return kids(el, local)[0] || null; }
function langOf(el) {
return el.getAttributeNS(XML_LANG, 'lang') || el.getAttribute('xml:lang') || '';
}
// Todas las traducciones de com:Name -> {en:'...', es:'...', fr:'...'}
function namesOf(el) {
var map = {};
kids(el, 'Name').forEach(function (n) {
map[langOf(n) || 'und'] = (n.textContent || '').trim();
});
return map;
}
function label(names) {
if (names[lang]) return names[lang];
for (var i = 0; i < LANG_FALLBACK.length; i++) {
if (names[LANG_FALLBACK[i]]) return names[LANG_FALLBACK[i]];
}
var keys = Object.keys(names);
return keys.length ? names[keys[0]] : '(sin nombre)';
}
// Anotación ORDER -> número usado para ordenar hermanos
function orderOf(el) {
var anns = firstKid(el, 'Annotations');
if (!anns) return Number.MAX_SAFE_INTEGER;
var list = kids(anns, 'Annotation');
for (var i = 0; i < list.length; i++) {
var type = firstKid(list[i], 'AnnotationType');
if (type && type.textContent.trim().toUpperCase() === 'ORDER') {
var txt = firstKid(list[i], 'AnnotationText');
var v = parseFloat(txt ? txt.textContent : '');
return isNaN(v) ? Number.MAX_SAFE_INTEGER : v;
}
}
return...