Skip to content

Commit

Permalink
Switch from JSON responses + JS templating to htmx
Browse files Browse the repository at this point in the history
  • Loading branch information
inducer committed Jul 29, 2024
1 parent bda1d10 commit 6a7a33d
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 190 deletions.
25 changes: 9 additions & 16 deletions course/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2080,11 +2080,6 @@ def view_flow_page(
"viewing_prior_version": viewing_prior_version,
"prev_answer_visits": prev_answer_visits,
"prev_visit_id": prev_visit_id,

# Wrappers used by JavaScript template (tmpl) so as not to
# conflict with Django template's tag wrapper
"JQ_OPEN": "{%",
"JQ_CLOSE": "%}",
}

if fpctx.page.expects_answer() and fpctx.page.is_answer_gradable():
Expand All @@ -2103,7 +2098,8 @@ def view_flow_page(


@course_view
def get_prev_answer_visits_dropdown_content(pctx, flow_session_id, page_ordinal):
def get_prev_answer_visits_dropdown_content(
pctx, flow_session_id, page_ordinal, prev_visit_id):
"""
:return: serialized prev_answer_visits items for past-submission-dropdown
"""
Expand All @@ -2120,16 +2116,13 @@ def get_prev_answer_visits_dropdown_content(pctx, flow_session_id, page_ordinal)
FlowPageData, flow_session=flow_session, page_ordinal=page_ordinal)
prev_answer_visits = get_prev_answer_visits_qset(page_data)

def serialize(obj):
return {
"id": obj.id,
"visit_time": (
format_datetime_local(as_local_time(obj.visit_time))),
"is_submitted_answer": obj.is_submitted_answer,
}

return http.JsonResponse(
{"result": [serialize(visit) for visit in prev_answer_visits]})
return render(request, "course/flow-page-prev-visits.html", {
"prev_answer_visits": prev_answer_visits,
"prev_visit_id": (
None
if prev_visit_id == "None" else
int(prev_visit_id)),
})


def get_pressed_button(form: StyledForm) -> str:
Expand Down
31 changes: 10 additions & 21 deletions course/grading.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
PermissionDenied,
SuspiciousOperation,
)
from django.shortcuts import get_object_or_404, redirect # noqa
from django.shortcuts import get_object_or_404, redirect, render # noqa
from django.utils.translation import gettext as _

from course.constants import participation_permission as pperm
Expand All @@ -53,8 +53,6 @@
)
from course.views import get_now_or_fake_time
from relate.utils import (
as_local_time,
format_datetime_local,
retry_transaction_decorator,
)

Expand Down Expand Up @@ -92,7 +90,8 @@ def get_prev_visit_grades(


@course_view
def get_prev_grades_dropdown_content(pctx, flow_session_id, page_ordinal):
def get_prev_grades_dropdown_content(pctx, flow_session_id, page_ordinal,
prev_grade_id):
"""
:return: serialized prev_grades items for rendering past-grades-dropdown
"""
Expand All @@ -111,17 +110,13 @@ def get_prev_grades_dropdown_content(pctx, flow_session_id, page_ordinal):
prev_grades = get_prev_visit_grades(pctx.course_identifier,
flow_session_id, page_ordinal, True)

def serialize(obj):
return {
"id": obj.id,
"visit_time": (
format_datetime_local(as_local_time(obj.visit.visit_time))),
"grade_time": format_datetime_local(as_local_time(obj.grade_time)),
"value": obj.value(),
}

return http.JsonResponse(
{"result": [serialize(pgrade) for pgrade in prev_grades]})
return render(pctx.request, "course/prev-grades-dropdown.html", {
"prev_grades": prev_grades,
"prev_grade_id": (
None
if prev_grade_id == "None" else
int(prev_grade_id)),
})


# {{{ grading driver
Expand Down Expand Up @@ -398,12 +393,6 @@ def grade_flow_page(
"correct_answer": fpctx.page.correct_answer(
fpctx.page_context, fpctx.page_data.data,
answer_data, grade_data),


# Wrappers used by JavaScript template (tmpl) so as not to
# conflict with Django template's tag wrapper
"JQ_OPEN": "{%",
"JQ_CLOSE": "%}",
})


Expand Down
21 changes: 21 additions & 0 deletions course/templates/course/flow-page-prev-visits.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{% load i18n %}

{% if not prev_answer_visits %}
<li><h6 class="dropdown-header">{% trans 'No submission history' %}</h6> </li>
{% else %}
{% for visit in prev_answer_visits %}
<li>
{% if forloop.first %}
<a class="dropdown-item" href="?">
{% else %}
<a class="dropdown-item" href="?visit_id={{ visit.id }}">
{% endif %}
{% if visit.id == prev_visit_id %}<b>{% endif %}
{{ visit.visit_time }}
{% if forloop.first %}(current){% endif %}
{% if visit.id == prev_visit_id %}</b>{% endif %}
{% if visit.is_submitted_answer %}(submitted){% endif %}
</a>
</li>
{% endfor %}
{% endif %}
59 changes: 10 additions & 49 deletions course/templates/course/flow-page.html
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,19 @@
{% if expects_answer %}
<div class="relate-flow-page-past-submissions">
<div class="dropdown" id="past-submission_dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="past-submission_dropdown_button" data-bs-toggle="dropdown"
<button
class="btn btn-outline-secondary dropdown-toggle"
type="button" id="past-submission_dropdown_button"
hx-get="{% url "relate-get_prev_answer_visits_dropdown_content" course.identifier flow_session.id page_ordinal prev_visit_id %}"
hx-target="#past-submission_dropdown_content"
data-bs-toggle="dropdown"
title='{% trans "Submission history for this page" %}'>
<i class="bi bi-clock-history"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow" aria-labelledby="past-submission_dropdown_button" id="past-submission_dropdown_content">
<ul
class="dropdown-menu dropdown-menu-end shadow"
aria-labelledby="past-submission_dropdown_button"
id="past-submission_dropdown_content">
<li style="list-style:none; position:relative; left:50%;">
<i class="bi bi-hourglass"></i>
</li>
Expand Down Expand Up @@ -545,51 +553,4 @@

{% endblock %}

{% block page_bottom_javascript_extra %}

{% if expects_answer %}
<script>
function generate_past_submission_dropdown_content(){
var jqxhr = $.ajax({
url: "{% url "relate-get_prev_answer_visits_dropdown_content" course.identifier flow_session.id page_ordinal %}"
}).done(function (result) {
if(result.result)
document.getElementById("past-submission_dropdown_content").innerHTML = tmpl("tmpl-prev-answer-visits", result.result);
});
}

$('#past-submission_dropdown').on('shown.bs.dropdown', generate_past_submission_dropdown_content);


</script>

{# https://github.com/blueimp/JavaScript-Templates #}
<script type="text/x-tmpl" id="tmpl-prev-answer-visits">
{{ JQ_OPEN }} if (o.length==0) { {{ JQ_CLOSE }}
<li><h6 class="dropdown-header">{% trans 'No submission history' %}</h6> </li>
{{ JQ_OPEN }} } else { {{ JQ_CLOSE }}
{{ JQ_OPEN }} for (var i=0; i<o.length; i++) { {{ JQ_CLOSE }}
<li>
{{ JQ_OPEN }} if (i==0) { {{ JQ_CLOSE }}
<a class="dropdown-item" href="?">
{{ JQ_OPEN }} } else { {{ JQ_CLOSE }}
<a class="dropdown-item" href="?visit_id={{ JQ_OPEN }}=o[i].id{{ JQ_CLOSE }}">
{{ JQ_OPEN }} } {{ JQ_CLOSE }}
{{ JQ_OPEN }} if (o[i].id=={{ prev_visit_id }}) { {{ JQ_CLOSE }}<b>{{ JQ_OPEN }} } {{ JQ_CLOSE }}
{{ JQ_OPEN }}=o[i].visit_time{{ JQ_CLOSE }}
{{ JQ_OPEN }} if (o[i].id=={{ prev_visit_id }}) { {{ JQ_CLOSE }}</b>{{ JQ_OPEN }} } {{ JQ_CLOSE }}
{{ JQ_OPEN }} if (i==0) { {{ JQ_CLOSE }}
{% trans "(current)" %}
{{ JQ_OPEN }} } {{ JQ_CLOSE }}
{{ JQ_OPEN }} if (o[i].is_submitted_answer) { {{ JQ_CLOSE }}
{% trans "(submitted)" %}
{{ JQ_OPEN }} } {{ JQ_CLOSE }}
</a>
</li>
{{ JQ_OPEN }} } {{ JQ_CLOSE }}
{{ JQ_OPEN }} } {{ JQ_CLOSE }}
</script>
{% endif %}
{{ block.super }}
{% endblock %}
{# vim: set foldmethod=marker: #}
63 changes: 9 additions & 54 deletions course/templates/course/grade-flow-page.html
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,18 @@ <h1> {% trans "Grading" %}: <tt>{{ flow_identifier}} - {{ page_data.group_id }}/

{% if expects_answer %}
<div class="dropdown" id="past-submission-and-grades_dropdown">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" id="past-submission-and-grades_dropdown_button"
data-bs-toggle="dropdown">
<button
class="btn btn-sm btn-outline-secondary dropdown-toggle"
type="button" id="past-submission-and-grades_dropdown_button"
hx-get='{% url "relate-get_prev_grades_dropdown_content" course.identifier flow_session.id page_ordinal prev_grade_id %}'
hx-target="#past-submission-and-grades_dropdown_content"
data-bs-toggle="dropdown">
{% trans "Past submissions/grades" %}
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu shadow" aria-labelledby="past-submission-and-grades_dropdown_button" id="past-submission-and-grades_dropdown_content">
<ul class="dropdown-menu dropdown-menu shadow"
aria-labelledby="past-submission-and-grades_dropdown_button"
id="past-submission-and-grades_dropdown_content">
<li style="list-style:none; position:relative; left:50%;">
<i class="bi bi-hourglass"></i>
</li>
Expand Down Expand Up @@ -472,55 +478,4 @@ <h1> {% trans "Grading" %}: <tt>{{ flow_identifier}} - {{ page_data.group_id }}/

{% endblock %}

{% block page_bottom_javascript_extra %}

{% if expects_answer %}
<script>

function generate_past_submission_and_grades_dropdown_content(){
var jqxhr = $.ajax({
url: "{% url "relate-get_prev_grades_dropdown_content" course.identifier flow_session.id page_ordinal %}"
}).done(function (result) {
if(result.result)
document.getElementById("past-submission-and-grades_dropdown_content").innerHTML = tmpl("tmpl-prev-grades", result.result);
});
}

$('#past-submission-and-grades_dropdown').on('shown.bs.dropdown', generate_past_submission_and_grades_dropdown_content);

</script>

{# https://github.com/blueimp/JavaScript-Templates #}
<script type="text/x-tmpl" id="tmpl-prev-grades">
{{ JQ_OPEN }} if (o.length==0) { {{ JQ_CLOSE }}
<li><h6 class="dropdown-header">{% trans 'No submission/grade history' %}</h6><li>
{{ JQ_OPEN }} } else { {{ JQ_CLOSE }}
{{ JQ_OPEN }} for (var i=0; i<o.length; i++) { {{ JQ_CLOSE }}
<li>
{{ JQ_OPEN }} if (i==0) { {{ JQ_CLOSE }}
<a class="dropdown-item" href="?">
{{ JQ_OPEN }} } else { {{ JQ_CLOSE }}
<a class="dropdown-item" href="?grade_id={{ JQ_OPEN }}=o[i].id{{ JQ_CLOSE }}">
{{ JQ_OPEN }} } {{ JQ_CLOSE }}
{{ JQ_OPEN }} if (o[i].id=={{ prev_grade_id }}) { {{ JQ_CLOSE }}<b>{{ JQ_OPEN }} } {{ JQ_CLOSE }}
{% trans "Submission:" %} {{ JQ_OPEN }}=o[i].visit_time{{ JQ_CLOSE }} &middot; {% trans "Grade:" %} {{ JQ_OPEN }}=o[i].grade_time{{ JQ_CLOSE }}
{{ JQ_OPEN }} if (o[i].id=={{ prev_grade_id }}) { {{ JQ_CLOSE }}</b>{{ JQ_OPEN }} } {{ JQ_CLOSE }}
{{ JQ_OPEN }} if (i==0) { {{ JQ_CLOSE }}
{% trans "(current)" %}
{{ JQ_OPEN }} } {{ JQ_CLOSE }}
{{ JQ_OPEN }} if (!o[i].value) { {{ JQ_CLOSE }}
[{% trans "no grade" %}]
{{ JQ_OPEN }} } else { {{ JQ_CLOSE }}
[{{ JQ_OPEN }}=o[i].value{{ JQ_CLOSE }}
{{ JQ_OPEN }} if (o[i].value <=1) { {{ JQ_CLOSE }}{% trans "point" %}{{ JQ_OPEN }} } else { {{ JQ_CLOSE }}{% trans "points" %}{{ JQ_OPEN }} } {{ JQ_CLOSE }}]
{{ JQ_OPEN }} } {{ JQ_CLOSE }}
</a>
</li>
{{ JQ_OPEN }} } {{ JQ_CLOSE }}
{{ JQ_OPEN }} } {{ JQ_CLOSE }}
</script>
{% endif %}
{{ block.super }}
{% endblock %}

{# vim: set foldmethod=marker: #}
25 changes: 25 additions & 0 deletions course/templates/course/prev-grades-dropdown.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{% load i18n %}

{% if not prev_grades %}
<li><h6 class="dropdown-header">{% trans 'No submission/grade history' %}</h6><li>
{% else %}
{% for grade in prev_grades %}
{% if forloop.first %}
<a class="dropdown-item" href="?">
{% else %}
<a class="dropdown-item" href="?grade_id={{ grade.id }}">
{% endif %}
{% if grade.id == prev_grade_id %}<b>{% endif %}
{{ grade.visit.visit_time }}
&middot;
{% trans "Grade:" %} {{ grade.grade_time }}
{% if grade.id == prev_grade_id %}</b>{% endif %}
{% if grade.value == none %}
[{% trans "no grade" %}]
{% else %}
[{{ grade.value }} {% trans "points" %}]
{% endif %}
{% if forloop.first %}(current){% endif %}
</a>
{% endfor %}
{% endif %}
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"@fullcalendar/list": "^5.10.1",
"@fullcalendar/timegrid": "^5.10.1",
"@popperjs/core": "^2.11.2",
"blueimp-tmpl": "^3.11.0",
"bootstrap": "^5.3.1",
"@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.29",
Expand Down Expand Up @@ -34,7 +33,8 @@
"mathjax": "^3.2.0",
"select2": "^4.0.5",
"select2-bootstrap-theme": "^0.1.0-beta.10",
"video.js": "^7.14.3"
"video.js": "^7.14.3",
"htmx.org": "^2"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^17.1.0",
Expand Down
3 changes: 0 additions & 3 deletions relate/static/js/base-with-markup.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// FIXME: blueimp-tmpl is deprecated. Only used in flow-page and grade-flow-page.
import tmpl from 'blueimp-tmpl';

import './base';
import 'jstree';
import 'video.js';
Expand Down
13 changes: 13 additions & 0 deletions relate/static/js/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,23 @@ import select2 from 'select2';
import * as rlUtils from './rlUtils';
import * as bsUtils from './bsUtils';

import 'htmx.org';

import '../css/base.scss';

select2(jQuery);

document.addEventListener('DOMContentLoaded', () => {
// document.body is not available until the DOM is loaded.
document.body.addEventListener('htmx:responseError', (evt) => {
bsUtils.showToast(
`HTMX request failed: ${evt.detail.xhr.status}:
${evt.detail.xhr.statusText}
(${evt.detail.xhr.responseURL})`,
);
});
});

globalThis.rlUtils = rlUtils;
globalThis.bsUtils = bsUtils;
globalThis.bootstrap = bootstrap;
Loading

0 comments on commit 6a7a33d

Please sign in to comment.