Foutstaat met opnieuw proberen Nieuw
Widget die netjes faalt: laden, fout met "Opnieuw proberen" (de knop krijgt een laadstaat) en herstel. Plus een offline-banner die luistert naar de online/offline-events van navigator en bij herstel zelf opnieuw laadt; met een demoknop simuleer je offline. In productie hang je de fetch van je eigen API (agenda, reviews) aan load().
Vrije plekken deze week
Fysiotherapie Kralingen
Beschikbaarheid laden...
Laden lukte niet
De agenda reageert even niet. Je keuzes zijn bewaard.
Zo gebruik je het
- Plak de HTML op de plek waar het onderdeel moet komen.
- Zet de CSS in je stylesheet. Alle klassen hebben een eigen voorvoegsel, dus niets botst met je eigen opmaak.
- Voeg de JavaScript toe en roep init() aan met het element waar de HTML in staat.
<!-- Foutstaat met opnieuw proberen · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="fss">
<div class="fss__bar"><span class="fss__eyebrow">Demo</span><button type="button" class="fss__ctl fss__off" aria-pressed="false">Simuleer offline</button><button type="button" class="fss__ctl fss__restart">Opnieuw starten</button></div>
<div class="fss__banner" role="status" hidden><svg viewBox="0 0 20 20" aria-hidden="true"><path d="M3 7.5a10 10 0 0 1 14 0M5.5 10.5a6.5 6.5 0 0 1 9 0M8.2 13.4a2.6 2.6 0 0 1 3.6 0M3 3l14 14"/></svg><span class="fss__btxt">Je bent offline. We laden opnieuw zodra je weer verbinding hebt.</span></div>
<div class="fss__card">
<div class="fss__head"><p class="fss__title">Vrije plekken deze week</p><p class="fss__sub">Fysiotherapie Kralingen</p></div>
<div class="fss__body" aria-live="polite" aria-busy="true">
<div class="fss__view fss__view--load"><span class="fss__spin" aria-hidden="true"></span><span>Beschikbaarheid laden...</span></div>
<div class="fss__view fss__view--err" hidden><svg class="fss__eico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7.5v5.5M12 16.2v.3"/></svg><div class="fss__etxt"><p class="fss__eh">Laden lukte niet</p><p class="fss__ep">De agenda reageert even niet. Je keuzes zijn bewaard.</p><button type="button" class="fss__retry"><span class="fss__rspin" aria-hidden="true"></span><span class="fss__rlbl">Opnieuw proberen</span></button></div></div>
<div class="fss__view fss__view--ok" hidden><ul class="fss__slots"><li><button type="button">di 14 okt<b>08:30</b></button></li><li><button type="button">di 14 okt<b>16:45</b></button></li><li><button type="button">do 16 okt<b>10:00</b></button></li><li><button type="button">vr 17 okt<b>13:15</b></button></li></ul></div>
</div>
</div>
</div>
</div>/* Foutstaat met opnieuw proberen · NedDev · eigen werk. */
.fss{position:absolute;inset:0;overflow:auto;display:flex;flex-direction:column;gap:8px;padding:10px 12px 12px;box-sizing:border-box;background:#f4f3f1;color:#0a0a0a;font-family:'Switzer',system-ui,sans-serif;text-align:left}
.fss *,.fss *::before,.fss *::after{box-sizing:border-box}
.fss__bar{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.fss__eyebrow{font:500 10px/1 'JetBrains Mono',ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase;color:#5c5a57;margin-right:2px}
.fss__ctl{font:500 11px/1 'JetBrains Mono',ui-monospace,monospace;color:#0a0a0a;background:#fff;border:1px solid #cfccc7;border-radius:999px;padding:6px 10px;cursor:pointer;transition:border-color .15s,background .15s,color .15s}
.fss__ctl:hover{border-color:#0a0a0a}
.fss__ctl[aria-pressed="true"]{background:#0a0a0a;color:#fff;border-color:#0a0a0a}
.fss__ctl:focus-visible,.fss__retry:focus-visible,.fss__slots button:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.fss__banner{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:4px;background:#0a0a0a;color:#fff;font-size:12px;line-height:1.35}
.fss__banner[hidden]{display:none}
.fss__banner.is-back{background:#1d6b3f}
.fss__banner svg{flex:none;width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round}
.fss__card{flex:1;min-height:120px;background:#fff;border:1px solid #e0ddd8;border-radius:6px;display:flex;flex-direction:column}
.fss__head{padding:10px 12px 8px;border-bottom:1px solid #eeebe7}
.fss__title{margin:0;font-size:14px;font-weight:600}
.fss__sub{margin:2px 0 0;font:500 10px/1.2 'JetBrains Mono',ui-monospace,monospace;color:#5c5a57;letter-spacing:.04em}
.fss__body{flex:1;display:flex;padding:10px 12px}
.fss__view{flex:1;display:flex;align-items:center;gap:10px}
.fss__view[hidden]{display:none}
.fss__view--load{justify-content:center;font-size:13px;color:#5c5a57}
.fss__spin,.fss__rspin{width:16px;height:16px;border:2px solid #e0ddd8;border-top-color:#0a0a0a;border-radius:50%;animation:fss-spin .8s linear infinite}
.fss__view--err{align-items:flex-start}
.fss__eico{flex:none;width:22px;height:22px;fill:none;stroke:#B42318;stroke-width:1.8;stroke-linecap:round}
.fss__eh{margin:1px 0 2px;font-size:14px;font-weight:600}
.fss__ep{margin:0 0 8px;font-size:12.5px;line-height:1.4;color:#5c5a57}
.fss__retry{display:inline-flex;align-items:center;gap:8px;font:600 13px/1 'Switzer',system-ui,sans-serif;padding:9px 13px;border-radius:4px;border:1px solid #0a0a0a;background:#0a0a0a;color:#fff;cursor:pointer;transition:background .15s}
.fss__retry:hover:not(:disabled){background:#2e2c2a}
.fss__retry:active:not(:disabled){transform:translateY(1px)}
.fss__retry:disabled{cursor:progress;background:#3d3b38}
.fss__rspin{display:none;width:13px;height:13px;border-color:rgba(255,255,255,.35);border-top-color:#fff}
.fss__retry[aria-busy="true"] .fss__rspin{display:block}
.fss__slots{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:6px;width:100%;align-self:flex-start}
.fss__slots button{width:100%;display:flex;flex-direction:column;align-items:flex-start;gap:2px;font:500 10px/1.2 'JetBrains Mono',ui-monospace,monospace;color:#5c5a57;background:#fff;border:1px solid #cfccc7;border-radius:4px;padding:7px 9px;cursor:pointer;transition:border-color .15s,box-shadow .15s}
.fss__slots b{font:600 15px/1.1 'Switzer',system-ui,sans-serif;color:#0a0a0a;font-variant-numeric:tabular-nums}
.fss__slots button:hover{border-color:#0a0a0a}
.fss__slots button[aria-pressed="true"]{border-color:#F5480F;box-shadow:inset 0 0 0 1px #F5480F}
.fss__view--ok.is-in{animation:fss-in .3s ease both}
@keyframes fss-spin{to{transform:rotate(360deg)}}
@keyframes fss-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.fss__spin,.fss__rspin{animation-duration:2.4s}.fss__view--ok.is-in{animation:none}}// Foutstaat met opnieuw proberen · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.fss');if(!w)return;
var body=w.querySelector('.fss__body'),vLoad=w.querySelector('.fss__view--load'),vErr=w.querySelector('.fss__view--err'),vOk=w.querySelector('.fss__view--ok');
var retry=w.querySelector('.fss__retry'),rl=w.querySelector('.fss__rlbl'),eh=w.querySelector('.fss__eh'),ep=w.querySelector('.fss__ep');
var banner=w.querySelector('.fss__banner'),btxt=w.querySelector('.fss__btxt'),offBtn=w.querySelector('.fss__off');
var sim=false,attempt=0,state='load',timer,bt;
function offline(){return sim||navigator.onLine===false;}
function show(s){state=s;vLoad.hidden=s!=='load';vErr.hidden=s!=='err';vOk.hidden=s!=='ok';body.setAttribute('aria-busy',s==='load'?'true':'false');if(s==='ok'){vOk.classList.remove('is-in');void vOk.offsetWidth;vOk.classList.add('is-in');}}
function fail(off){eh.textContent=off?'Geen verbinding':'Laden lukte niet';ep.textContent=off?'Zodra je weer online bent, laden we de agenda vanzelf opnieuw.':'De agenda reageert even niet. Je keuzes zijn bewaard.';show('err');}
function load(fromBtn){clearTimeout(timer);attempt++;
if(fromBtn){retry.disabled=true;retry.setAttribute('aria-busy','true');rl.textContent='Bezig met laden';}else show('load');
timer=setTimeout(function(){retry.disabled=false;retry.removeAttribute('aria-busy');rl.textContent='Opnieuw proberen';
if(offline())fail(true);else if(attempt===1)fail(false);else show('ok');
if(fromBtn&&state==='err')retry.focus();},1100);}
retry.addEventListener('click',function(){load(true);});
w.querySelector('.fss__restart').addEventListener('click',function(){attempt=0;load(false);});
vOk.addEventListener('click',function(e){var b=e.target.closest('button');if(!b)return;vOk.querySelectorAll('button').forEach(function(x){x.setAttribute('aria-pressed',x===b?'true':'false');});});
vOk.querySelectorAll('button').forEach(function(x){x.setAttribute('aria-pressed','false');});
function net(){var off=offline();clearTimeout(bt);
if(off){banner.hidden=false;banner.classList.remove('is-back');btxt.textContent='Je bent offline. We laden opnieuw zodra je weer verbinding hebt.';}
else if(!banner.hidden){banner.classList.add('is-back');btxt.textContent='Weer online. We laden de agenda opnieuw.';bt=setTimeout(function(){banner.hidden=true;},2600);if(state==='err')load(false);}}
offBtn.addEventListener('click',function(){sim=!sim;offBtn.setAttribute('aria-pressed',sim?'true':'false');offBtn.textContent=sim?'Weer online':'Simuleer offline';net();});
window.addEventListener('online',net);window.addEventListener('offline',net);
net();load(false);
}
init(document.querySelector('.jouw-container'));
Meer uit componenten
Iets gezien dat je mooi vindt?
Zet het in je selectie en stuur hem op. Dan bouwen we je site eromheen, in jouw huisstijl.
Neem contact op →