| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
var LS_PREFIX = 'bkip_vote_'; |
| 5 |
|
| 6 |
function getStoredData(instanceId) { |
| 7 |
try { |
| 8 |
var raw = localStorage.getItem(LS_PREFIX + instanceId); |
| 9 |
return raw ? JSON.parse(raw) : null; |
| 10 |
} catch (e) { |
| 11 |
return null; |
| 12 |
} |
| 13 |
} |
| 14 |
|
| 15 |
function storeData(instanceId, data) { |
| 16 |
try { |
| 17 |
localStorage.setItem(LS_PREFIX + instanceId, JSON.stringify(data)); |
| 18 |
} catch (e) {} |
| 19 |
} |
| 20 |
|
| 21 |
function calcTotals(votes) { |
| 22 |
return votes.reduce(function (a, b) { return a + b; }, 0); |
| 23 |
} |
| 24 |
|
| 25 |
function renderResults(wrap, votes, animate) { |
| 26 |
var total = calcTotals(votes); |
| 27 |
var fills = wrap.querySelectorAll('.bkip-bar-fill'); |
| 28 |
var pcts = wrap.querySelectorAll('.bkip-result-pct'); |
| 29 |
var totalNum = wrap.querySelector('.bkip-total-num'); |
| 30 |
|
| 31 |
fills.forEach(function (fill, i) { |
| 32 |
var pct = total > 0 ? Math.round((votes[i] / total) * 100) : 0; |
| 33 |
|
| 34 |
if (!animate) { |
| 35 |
fill.style.transition = 'none'; |
| 36 |
} |
| 37 |
|
| 38 |
// Trigger transition by setting width after a tiny delay |
| 39 |
setTimeout(function () { |
| 40 |
fill.style.width = pct + '%'; |
| 41 |
}, animate ? 50 : 0); |
| 42 |
|
| 43 |
if (pcts[i]) pcts[i].textContent = pct + '%'; |
| 44 |
}); |
| 45 |
|
| 46 |
if (totalNum) totalNum.textContent = total; |
| 47 |
} |
| 48 |
|
| 49 |
function showResults(wrap) { |
| 50 |
var votesEl = wrap.querySelector('.bkip-votes'); |
| 51 |
var resultsEl = wrap.querySelector('.bkip-results'); |
| 52 |
if (votesEl) votesEl.classList.add('bkip-hidden'); |
| 53 |
if (resultsEl) resultsEl.classList.add('bkip-visible'); |
| 54 |
} |
| 55 |
|
| 56 |
function showVotes(wrap) { |
| 57 |
var votesEl = wrap.querySelector('.bkip-votes'); |
| 58 |
var resultsEl = wrap.querySelector('.bkip-results'); |
| 59 |
if (votesEl) votesEl.classList.remove('bkip-hidden'); |
| 60 |
if (resultsEl) resultsEl.classList.remove('bkip-visible'); |
| 61 |
} |
| 62 |
|
| 63 |
function initPoll(wrap) { |
| 64 |
var instanceId = wrap.dataset.instance; |
| 65 |
var allowRevote = wrap.dataset.allowRevote !== 'false'; |
| 66 |
var showBefore = wrap.dataset.showBefore === 'true'; |
| 67 |
var animate = wrap.dataset.animate !== 'false'; |
| 68 |
|
| 69 |
if (!instanceId) return; |
| 70 |
|
| 71 |
// Load option data from embedded JSON |
| 72 |
var dataScript = wrap.querySelector('.bkip-data'); |
| 73 |
var baseOptions; |
| 74 |
try { |
| 75 |
baseOptions = JSON.parse(dataScript ? dataScript.textContent : '[]'); |
| 76 |
} catch (e) { |
| 77 |
baseOptions = []; |
| 78 |
} |
| 79 |
|
| 80 |
// Initialise votes array from localStorage or base |
| 81 |
var stored = getStoredData(instanceId); |
| 82 |
var votes = stored ? stored.votes : baseOptions.map(function (o) { return o.votes || 0; }); |
| 83 |
var voted = stored ? stored.voted : false; |
| 84 |
|
| 85 |
// Initial render |
| 86 |
if (showBefore || voted) { |
| 87 |
renderResults(wrap, votes, false); |
| 88 |
if (voted) showResults(wrap); |
| 89 |
} |
| 90 |
|
| 91 |
// Vote buttons |
| 92 |
var btns = Array.prototype.slice.call(wrap.querySelectorAll('.bkip-option-btn')); |
| 93 |
btns.forEach(function (btn) { |
| 94 |
btn.addEventListener('click', function () { |
| 95 |
if (voted && !allowRevote) return; |
| 96 |
|
| 97 |
var idx = parseInt(btn.dataset.index, 10); |
| 98 |
if (isNaN(idx) || idx < 0 || idx >= votes.length) return; |
| 99 |
|
| 100 |
// If revote: subtract previous vote |
| 101 |
if (voted && stored && typeof stored.lastVote === 'number') { |
| 102 |
votes[stored.lastVote] = Math.max(0, votes[stored.lastVote] - 1); |
| 103 |
} |
| 104 |
|
| 105 |
votes[idx] = (votes[idx] || 0) + 1; |
| 106 |
voted = true; |
| 107 |
|
| 108 |
var newStored = { votes: votes, voted: true, lastVote: idx }; |
| 109 |
storeData(instanceId, newStored); |
| 110 |
stored = newStored; |
| 111 |
|
| 112 |
renderResults(wrap, votes, animate); |
| 113 |
showResults(wrap); |
| 114 |
}); |
| 115 |
}); |
| 116 |
|
| 117 |
// Revote button |
| 118 |
var revoteBtn = wrap.querySelector('.bkip-revote'); |
| 119 |
if (revoteBtn) { |
| 120 |
if (!allowRevote) { |
| 121 |
revoteBtn.style.display = 'none'; |
| 122 |
} |
| 123 |
revoteBtn.addEventListener('click', function () { |
| 124 |
if (!allowRevote) return; |
| 125 |
voted = false; |
| 126 |
showVotes(wrap); |
| 127 |
}); |
| 128 |
} |
| 129 |
} |
| 130 |
|
| 131 |
function init() { |
| 132 |
var polls = document.querySelectorAll('.bkip-wrap[data-instance]'); |
| 133 |
polls.forEach(initPoll); |
| 134 |
} |
| 135 |
|
| 136 |
if (document.readyState === 'loading') { |
| 137 |
document.addEventListener('DOMContentLoaded', init); |
| 138 |
} else { |
| 139 |
init(); |
| 140 |
} |
| 141 |
|
| 142 |
}()); |
| 143 |
|