Refactor answer click handling to prevent duplicate event listeners on the new mark2 page; streamline state management for answer marking.
This commit is contained in:
@@ -44,24 +44,27 @@ $(document).ready(function () {
|
||||
// $(element).append($("<span class='google-link' title='search for answer with google'><a href='https://www.google.com/search?q=" + $(element).text() + "' target='_blank'>G</a></span>"));
|
||||
//});
|
||||
|
||||
$(".answer-list span.answer").each(function (index, element) {
|
||||
console.log(element);
|
||||
// Legacy per-element click handler: skip on the new mark2 page which
|
||||
// manages clicks via a delegated handler (it uses #answer-list). This
|
||||
// prevents duplicate/colliding handlers when mark2 is rendered.
|
||||
if (!document.getElementById('answer-list')) {
|
||||
$(".answer-list span.answer").each(function (index, element) {
|
||||
console.log(element);
|
||||
|
||||
$(element).click(function (e) {
|
||||
$(element).click(function (e) {
|
||||
|
||||
var classes = ['answer correct', 'answer half-correct', 'answer incorrect'];
|
||||
$(element).each(function () {
|
||||
let mark = classes[($.inArray(this.className, classes) + 1) % classes.length];
|
||||
this.className = mark
|
||||
this.dataset.newmark = mark.split(" ")[1]
|
||||
});
|
||||
|
||||
prepAnswerData();
|
||||
|
||||
var classes = ['answer correct', 'answer half-correct', 'answer incorrect'];
|
||||
$(element).each(function () {
|
||||
let mark = classes[($.inArray(this.className, classes) + 1) % classes.length];
|
||||
this.className = mark
|
||||
this.dataset.newmark = mark.split(" ")[1]
|
||||
});
|
||||
|
||||
prepAnswerData();
|
||||
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
prepAnswerData();
|
||||
|
||||
if ($(".post-form").length > 0) {
|
||||
|
||||
@@ -114,6 +114,8 @@
|
||||
.answer-actions .action-icon { margin-left: 0.5rem; color: #6c757d; text-decoration: none; border: none; background: none; }
|
||||
.answer-actions .action-icon:hover { color: #000; text-decoration: none; }
|
||||
.answer-actions .copy-to-clipboard { cursor: pointer; }
|
||||
/* Active state for numeric mark buttons */
|
||||
.mark-btn.active { background-color: #0d6efd; color: #fff; border-color: #0d6efd; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -139,42 +141,41 @@
|
||||
|
||||
// Delegated click handler for answers — only updates local state.
|
||||
document.addEventListener('click', function(e){
|
||||
const el = e.target.closest('span.answer');
|
||||
if (!el) return;
|
||||
// Ignore clicks on action icons or mark controls
|
||||
if (e.target.closest('.answer-actions') || e.target.closest('.mark-controls') || e.target.closest('.copy-to-clipboard')) return;
|
||||
|
||||
// cycle states: not-marked -> correct -> half-correct -> incorrect -> not-marked
|
||||
// Prefer a clicked span.answer if present
|
||||
let spanEl = e.target.closest('span.answer');
|
||||
// Otherwise fall back to the list-item's span
|
||||
if (!spanEl) {
|
||||
const li = e.target.closest('#answer-list li');
|
||||
if (!li) return;
|
||||
spanEl = li.querySelector('span.answer');
|
||||
if (!spanEl) return;
|
||||
}
|
||||
|
||||
// Ensure span has an explicit state class
|
||||
if (!spanEl.classList.contains('correct') && !spanEl.classList.contains('half-correct') && !spanEl.classList.contains('incorrect') && !spanEl.classList.contains('not-marked')) {
|
||||
spanEl.classList.add('not-marked');
|
||||
}
|
||||
|
||||
// cycle states: not-marked -> correct -> half-correct -> incorrect -> not-marked
|
||||
const states = ['not-marked', 'correct', 'half-correct', 'incorrect'];
|
||||
// current may be 'answer correct' or just 'answer'
|
||||
let parts = el.className.split(/\s+/).filter(Boolean);
|
||||
// find current semantic state
|
||||
let currentState = 'not-marked';
|
||||
for (let p of parts) {
|
||||
for (let p of spanEl.classList) {
|
||||
if (states.includes(p)) { currentState = p; break; }
|
||||
}
|
||||
let idx = (states.indexOf(currentState) + 1) % states.length;
|
||||
const newState = states[idx];
|
||||
|
||||
// set class preserving 'answer' base
|
||||
el.className = 'answer ' + newState;
|
||||
// map to numeric for setMarkForSpan
|
||||
let numeric = null;
|
||||
if (newState === 'correct') numeric = 2;
|
||||
else if (newState === 'half-correct') numeric = 1;
|
||||
else if (newState === 'incorrect') numeric = 0;
|
||||
|
||||
// mark/unmark list item visually (no moving between lists in single-column mode)
|
||||
const li = el.closest('li');
|
||||
if (li) {
|
||||
// clear any previous per-state classes
|
||||
li.classList.remove('marked-correct','marked-half-correct','marked-incorrect','marked-item');
|
||||
if (newState === 'not-marked') {
|
||||
// nothing
|
||||
} else if (newState === 'correct') {
|
||||
li.classList.add('marked-item','marked-correct');
|
||||
} else if (newState === 'half-correct') {
|
||||
li.classList.add('marked-item','marked-half-correct');
|
||||
} else if (newState === 'incorrect') {
|
||||
li.classList.add('marked-item','marked-incorrect');
|
||||
}
|
||||
}
|
||||
|
||||
// update hidden form field so change is saved only on explicit Save/Next/Previous
|
||||
updateMarkedAnswersField();
|
||||
// delegate to setMarkForSpan to keep behaviour consistent (updates classes, active buttons, hidden field)
|
||||
setMarkForSpan(spanEl, numeric);
|
||||
|
||||
}, false);
|
||||
|
||||
@@ -213,6 +214,10 @@
|
||||
if (li.querySelector('.mark-controls')) return; // already attached
|
||||
const span = li.querySelector('span.answer');
|
||||
if (!span) return;
|
||||
// Ensure span has an explicit default state class so logic is consistent
|
||||
if (!span.classList.contains('correct') && !span.classList.contains('half-correct') && !span.classList.contains('incorrect') && !span.classList.contains('not-marked')) {
|
||||
span.classList.add('not-marked');
|
||||
}
|
||||
const controls = document.createElement('div');
|
||||
controls.className = 'mark-controls mt-2';
|
||||
const b2 = makeButton('2', 'mark-2');
|
||||
@@ -232,6 +237,20 @@
|
||||
b1.addEventListener('click', function(){ setMarkForSpan(span, 1); });
|
||||
b0.addEventListener('click', function(){ setMarkForSpan(span, 0); });
|
||||
bClear.addEventListener('click', function(){ setMarkForSpan(span, null); });
|
||||
|
||||
// set initial active button based on existing state
|
||||
(function(){
|
||||
let state = null;
|
||||
if (span.classList.contains('correct')) state = 2;
|
||||
else if (span.classList.contains('half-correct')) state = 1;
|
||||
else if (span.classList.contains('incorrect')) state = 0;
|
||||
// clear any pre-existing active flags then set the correct one
|
||||
[b2, b1, b0, bClear].forEach(b => b.classList.remove('active'));
|
||||
if (state === 2) b2.classList.add('active');
|
||||
else if (state === 1) b1.classList.add('active');
|
||||
else if (state === 0) b0.classList.add('active');
|
||||
else bClear.classList.add('active');
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -256,6 +275,24 @@
|
||||
li.classList.add('marked-item','marked-incorrect');
|
||||
}
|
||||
}
|
||||
// update active state of numeric controls for this list item
|
||||
try {
|
||||
const li = el.closest('li');
|
||||
if (li) {
|
||||
const btn2 = li.querySelector('.mark-2');
|
||||
const btn1 = li.querySelector('.mark-1');
|
||||
const btn0 = li.querySelector('.mark-0');
|
||||
const btnClear = li.querySelector('.mark-clear');
|
||||
[btn2, btn1, btn0, btnClear].forEach(b => { if (b) { b.classList.remove('active'); } });
|
||||
if (numeric === 2 && btn2) btn2.classList.add('active');
|
||||
else if (numeric === 1 && btn1) btn1.classList.add('active');
|
||||
else if (numeric === 0 && btn0) btn0.classList.add('active');
|
||||
else if (numeric === null && btnClear) btnClear.classList.add('active');
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
updateMarkedAnswersField();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +1,42 @@
|
||||
{% comment %}Renders only the list items for already-marked answers (used as a partial){% endcomment %}
|
||||
{% for answer in correct_answers %}
|
||||
{% with answer_url=answer|urlencode:'' %}
|
||||
<li class="list-group-item d-flex align-items-center justify-content-between">
|
||||
<pre class="mb-0 me-3 flex-grow-1"><span class="answer correct" title="{{ answer }}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer_url %}">{{ answer }}</span></pre>
|
||||
<div class="answer-actions text-end">
|
||||
<a class="action-icon" href="https://www.google.com/search?q={{ answer_url }}" target="_blank" title="Search Google">🔍</a>
|
||||
<button type="button" class="action-icon btn btn-link p-0 copy-to-clipboard" data-text="{{ answer|escapejs }}" title="Copy to clipboard">📋</button>
|
||||
<a class="action-icon" href="{% url 'anatomy:question_user_answers_by_compare' question.pk answer_url %}" target="_blank" title="View answers">🔗</a>
|
||||
<li class="list-group-item d-flex flex-column">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<pre class="mb-0 me-3 flex-grow-1"><span class="answer correct" title="{{ answer }}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer_url %}">{{ answer }}</span></pre>
|
||||
<div class="answer-actions text-end">
|
||||
<a class="action-icon" href="https://www.google.com/search?q={{ answer_url }}" target="_blank" title="Search Google">🔍</a>
|
||||
<button type="button" class="action-icon btn btn-link p-0 copy-to-clipboard" data-text="{{ answer|escapejs }}" title="Copy to clipboard">📋</button>
|
||||
<a class="action-icon" href="{% url 'anatomy:question_user_answers_by_compare' question.pk answer_url %}" target="_blank" title="View answers">🔗</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
{% for answer in half_mark_answers %}
|
||||
{% with answer_url=answer|urlencode:'' %}
|
||||
<li class="list-group-item d-flex align-items-center justify-content-between">
|
||||
<pre class="mb-0 me-3 flex-grow-1"><span class="answer half-correct" title="{{ answer }}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer_url %}">{{ answer }}</span></pre>
|
||||
<div class="answer-actions text-end">
|
||||
<a class="action-icon" href="https://www.google.com/search?q={{ answer_url }}" target="_blank" title="Search Google">🔍</a>
|
||||
<button type="button" class="action-icon btn btn-link p-0 copy-to-clipboard" data-text="{{ answer|escapejs }}" title="Copy to clipboard">📋</button>
|
||||
<a class="action-icon" href="{% url 'anatomy:question_user_answers_by_compare' question.pk answer_url %}" target="_blank" title="View answers">🔗</a>
|
||||
<li class="list-group-item d-flex flex-column">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<pre class="mb-0 me-3 flex-grow-1"><span class="answer half-correct" title="{{ answer }}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer_url %}">{{ answer }}</span></pre>
|
||||
<div class="answer-actions text-end">
|
||||
<a class="action-icon" href="https://www.google.com/search?q={{ answer_url }}" target="_blank" title="Search Google">🔍</a>
|
||||
<button type="button" class="action-icon btn btn-link p-0 copy-to-clipboard" data-text="{{ answer|escapejs }}" title="Copy to clipboard">📋</button>
|
||||
<a class="action-icon" href="{% url 'anatomy:question_user_answers_by_compare' question.pk answer_url %}" target="_blank" title="View answers">🔗</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
{% for answer in incorrect_answers %}
|
||||
{% with answer_url=answer|urlencode:'' %}
|
||||
<li class="list-group-item d-flex align-items-center justify-content-between">
|
||||
<pre class="mb-0 me-3 flex-grow-1"><span class="answer incorrect" title="{{ answer }}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer_url %}">{{ answer }}</span></pre>
|
||||
<div class="answer-actions text-end">
|
||||
<a class="action-icon" href="https://www.google.com/search?q={{ answer_url }}" target="_blank" title="Search Google">🔍</a>
|
||||
<button type="button" class="action-icon btn btn-link p-0 copy-to-clipboard" data-text="{{ answer|escapejs }}" title="Copy to clipboard">📋</button>
|
||||
<a class="action-icon" href="{% url 'anatomy:question_user_answers_by_compare' question.pk answer_url %}" target="_blank" title="View answers">🔗</a>
|
||||
<li class="list-group-item d-flex flex-column">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<pre class="mb-0 me-3 flex-grow-1"><span class="answer incorrect" title="{{ answer }}" data-answerurl="{% url 'anatomy:question_user_answers_by_compare' question.pk answer_url %}">{{ answer }}</span></pre>
|
||||
<div class="answer-actions text-end">
|
||||
<a class="action-icon" href="https://www.google.com/search?q={{ answer_url }}" target="_blank" title="Search Google">🔍</a>
|
||||
<button type="button" class="action-icon btn btn-link p-0 copy-to-clipboard" data-text="{{ answer|escapejs }}" title="Copy to clipboard">📋</button>
|
||||
<a class="action-icon" href="{% url 'anatomy:question_user_answers_by_compare' question.pk answer_url %}" target="_blank" title="View answers">🔗</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endwith %}
|
||||
|
||||
+16
-14
@@ -20,25 +20,27 @@ $(document).ready(function () {
|
||||
//$(".answer-list li").each(function (index, element) {
|
||||
// $(element).append($("<span class='google-link' title='search for answer with google'><a href='https://www.google.com/search?q=" + $(element).text() + "' target='_blank'>G</a></span>"));
|
||||
//});
|
||||
// Legacy per-element click handler: skip on the new mark2 page which
|
||||
// manages clicks via a delegated handler (it uses #answer-list). This
|
||||
// prevents duplicate/colliding handlers when mark2 is rendered.
|
||||
if (!document.getElementById('answer-list')) {
|
||||
$(".answer-list span.answer").each(function (index, element) {
|
||||
console.log(element);
|
||||
|
||||
$(".answer-list span.answer").each(function (index, element) {
|
||||
console.log(element);
|
||||
$(element).click(function (e) {
|
||||
|
||||
$(element).click(function (e) {
|
||||
var classes = ['answer correct', 'answer half-correct', 'answer incorrect'];
|
||||
$(element).each(function () {
|
||||
let mark = classes[($.inArray(this.className, classes) + 1) % classes.length];
|
||||
this.className = mark
|
||||
this.dataset.newmark = mark.split(" ")[1]
|
||||
});
|
||||
|
||||
prepAnswerData();
|
||||
|
||||
var classes = ['answer correct', 'answer half-correct', 'answer incorrect'];
|
||||
$(element).each(function () {
|
||||
let mark = classes[($.inArray(this.className, classes) + 1) % classes.length];
|
||||
this.className = mark
|
||||
this.dataset.newmark = mark.split(" ")[1]
|
||||
});
|
||||
|
||||
prepAnswerData();
|
||||
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
prepAnswerData();
|
||||
|
||||
if ($(".post-form").length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user