Compare commits
6 Commits
294f1e42f7
...
f14085f4c1
Author | SHA1 | Date | |
---|---|---|---|
f14085f4c1 | |||
be5343a4bd | |||
2da8eb7712 | |||
3a0abaac6a | |||
b15f76701e | |||
02290e968c |
@ -10,7 +10,7 @@ The exam client is made with accessibility in mind, and has been designed to be
|
|||||||
|
|
||||||
## Set Up and Installation
|
## Set Up and Installation
|
||||||
|
|
||||||
The clien is designed to work on a server.
|
The app is designed to be hosted on a server.
|
||||||
|
|
||||||
### Pre-Requisites
|
### Pre-Requisites
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ server {
|
|||||||
include /etc/nginx/ssl.conf;
|
include /etc/nginx/ssl.conf;
|
||||||
include /etc/nginx/certbot-challenge.conf;
|
include /etc/nginx/certbot-challenge.conf;
|
||||||
|
|
||||||
|
# Define locations for static files to be served by Nginx
|
||||||
location ^~ /quiz/static/ {
|
location ^~ /quiz/static/ {
|
||||||
include /etc/nginx/mime.types;
|
include /etc/nginx/mime.types;
|
||||||
alias /usr/share/nginx/html/quiz/static/;
|
alias /usr/share/nginx/html/quiz/static/;
|
||||||
@ -34,6 +35,12 @@ server {
|
|||||||
alias /usr/share/nginx/html/admin/editor/static/;
|
alias /usr/share/nginx/html/admin/editor/static/;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location ^~ /admin/view/static/ {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
alias /usr/share/nginx/html/admin/view/static/;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy to the main app for all other requests
|
||||||
location / {
|
location / {
|
||||||
include /etc/nginx/conf.d/proxy_headers.conf;
|
include /etc/nginx/conf.d/proxy_headers.conf;
|
||||||
proxy_pass http://reftest;
|
proxy_pass http://reftest;
|
||||||
|
@ -46,11 +46,13 @@ def create_app():
|
|||||||
from .quiz.views import quiz
|
from .quiz.views import quiz
|
||||||
from .views import views
|
from .views import views
|
||||||
from .editor.views import editor
|
from .editor.views import editor
|
||||||
|
from .view.views import view
|
||||||
|
|
||||||
app.register_blueprint(admin, url_prefix='/admin')
|
app.register_blueprint(admin, url_prefix='/admin')
|
||||||
app.register_blueprint(api, url_prefix='/api')
|
app.register_blueprint(api, url_prefix='/api')
|
||||||
app.register_blueprint(views)
|
app.register_blueprint(views)
|
||||||
app.register_blueprint(quiz)
|
app.register_blueprint(quiz)
|
||||||
app.register_blueprint(editor, url_prefix='/admin/editor')
|
app.register_blueprint(editor, url_prefix='/admin/editor')
|
||||||
|
app.register_blueprint(view, url_prefix='/admin/view')
|
||||||
|
|
||||||
return app
|
return app
|
@ -1,25 +1,25 @@
|
|||||||
// Menu Highlight Scripts
|
// Menu Highlight Scripts
|
||||||
const menuItems = document.getElementsByClassName('nav-link');
|
const menuItems = document.getElementsByClassName('nav-link')
|
||||||
for(let i = 0; i < menuItems.length; i++) {
|
for(let i = 0; i < menuItems.length; i++) {
|
||||||
if(menuItems[i].pathname == window.location.pathname) {
|
if(menuItems[i].pathname == window.location.pathname) {
|
||||||
menuItems[i].classList.add('active');
|
menuItems[i].classList.add('active')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const dropdownItems = document.getElementsByClassName('dropdown-item');
|
const dropdownItems = document.getElementsByClassName('dropdown-item')
|
||||||
for(let i = 0; i< dropdownItems.length; i++) {
|
for(let i = 0; i< dropdownItems.length; i++) {
|
||||||
if(dropdownItems[i].pathname == window.location.pathname) {
|
if(dropdownItems[i].pathname == window.location.pathname) {
|
||||||
dropdownItems[i].classList.add('active');
|
dropdownItems[i].classList.add('active')
|
||||||
$( "#" + dropdownItems[i].id ).closest( '.dropdown' ).find('.dropdown-toggle').addClass('active');
|
$( "#" + dropdownItems[i].id ).closest( '.dropdown' ).find('.dropdown-toggle').addClass('active')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// General Post Method Form Processing Script
|
// General Post Method Form Processing Script
|
||||||
$('form.form-post').submit(function(event) {
|
$('form.form-post').submit(function(event) {
|
||||||
|
|
||||||
var $form = $(this);
|
var $form = $(this)
|
||||||
var data = $form.serialize();
|
var data = $form.serialize()
|
||||||
var url = $(this).prop('action');
|
var url = $(this).prop('action')
|
||||||
var rel_success = $(this).data('rel-success');
|
var rel_success = $(this).data('rel-success')
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
@ -28,25 +28,25 @@ $('form.form-post').submit(function(event) {
|
|||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.redirect_to) {
|
if (response.redirect_to) {
|
||||||
window.location.href = response.redirect_to;
|
window.location.href = response.redirect_to
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
window.location.href = rel_success;
|
window.location.href = rel_success
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(response) {
|
error: function(response) {
|
||||||
error_response(response);
|
error_response(response)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
// Form Upload Questions - Special case, needs to handle files.
|
// Form Upload Questions - Special case, needs to handle files.
|
||||||
$('form[name=form-upload-questions]').submit(function(event) {
|
$('form[name=form-upload-questions]').submit(function(event) {
|
||||||
|
|
||||||
var $form = $(this);
|
var $form = $(this)
|
||||||
var data = new FormData($form[0]);
|
var data = new FormData($form[0])
|
||||||
var file = $('input[name=data_file]')[0].files[0]
|
var file = $('input[name=data_file]')[0].files[0]
|
||||||
data.append('file', file)
|
data.append('file', file)
|
||||||
|
|
||||||
@ -57,21 +57,21 @@ $('form[name=form-upload-questions]').submit(function(event) {
|
|||||||
processData: false,
|
processData: false,
|
||||||
contentType: false,
|
contentType: false,
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
window.location.reload();
|
window.location.reload()
|
||||||
},
|
},
|
||||||
error: function(response) {
|
error: function(response) {
|
||||||
error_response(response);
|
error_response(response)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
// Edit and Delete Test Button Handlers
|
// Edit and Delete Test Button Handlers
|
||||||
$('.test-action').click(function(event) {
|
$('.test-action').click(function(event) {
|
||||||
|
|
||||||
let id = $(this).data('id');
|
let id = $(this).data('id')
|
||||||
let action = $(this).data('action');
|
let action = $(this).data('action')
|
||||||
|
|
||||||
if (action == 'delete' || action == 'start' || action == 'end') {
|
if (action == 'delete' || action == 'start' || action == 'end') {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@ -80,25 +80,25 @@ $('.test-action').click(function(event) {
|
|||||||
data: JSON.stringify({'id': id, 'action': action}),
|
data: JSON.stringify({'id': id, 'action': action}),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
window.location.href = '/admin/tests/';
|
window.location.href = '/admin/tests/'
|
||||||
},
|
},
|
||||||
error: function(response){
|
error: function(response){
|
||||||
error_response(response);
|
error_response(response)
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
} else if (action == 'edit') {
|
} else if (action == 'edit') {
|
||||||
window.location.href = `/admin/test/${id}/`
|
window.location.href = `/admin/test/${id}/`
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
// Edit Dataset Button Handlers
|
// Edit Dataset Button Handlers
|
||||||
$('.edit-question-dataset').click(function(event) {
|
$('.edit-question-dataset').click(function(event) {
|
||||||
|
|
||||||
var id = $(this).data('id');
|
var id = $(this).data('id')
|
||||||
var action = $(this).data('action');
|
var action = $(this).data('action')
|
||||||
var disabled = $(this).hasClass('disabled');
|
var disabled = $(this).hasClass('disabled')
|
||||||
|
|
||||||
if ( !disabled ) {
|
if ( !disabled ) {
|
||||||
if (action == 'delete') {
|
if (action == 'delete') {
|
||||||
@ -111,25 +111,27 @@ $('.edit-question-dataset').click(function(event) {
|
|||||||
}),
|
}),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
window.location.reload();
|
window.location.reload()
|
||||||
},
|
},
|
||||||
error: function(response){
|
error: function(response){
|
||||||
error_response(response);
|
error_response(response)
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
} else if (action == 'edit') {
|
} else if (action == 'edit') {
|
||||||
window.location.href = `/admin/editor/${id}/`
|
window.location.href = `/admin/editor/${id}/`
|
||||||
|
} else if (action == 'view') {
|
||||||
|
window.location.href = `/admin/view/${id}`
|
||||||
} else if (action == 'download') {
|
} else if (action == 'download') {
|
||||||
window.location.href = `/admin/settings/questions/download/${id}/`
|
window.location.href = `/admin/settings/questions/download/${id}/`
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
function error_response(response) {
|
function error_response(response) {
|
||||||
|
|
||||||
const $alert = $("#alert-box");
|
const $alert = $("#alert-box")
|
||||||
$alert.html('');
|
$alert.html('')
|
||||||
|
|
||||||
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
||||||
$alert.html(`
|
$alert.html(`
|
||||||
@ -138,18 +140,18 @@ function error_response(response) {
|
|||||||
${response.responseJSON.error}
|
${response.responseJSON.error}
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`)
|
||||||
} else if (response.responseJSON.error instanceof Array) {
|
} else if (response.responseJSON.error instanceof Array) {
|
||||||
var output = ''
|
var output = ''
|
||||||
for (var i = 0; i < response.responseJSON.error.length; i ++) {
|
for (let i = 0; i < response.responseJSON.error.length; i ++) {
|
||||||
output += `
|
output += `
|
||||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
||||||
${response.responseJSON.error[i]}
|
${response.responseJSON.error[i]}
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`
|
||||||
$alert.html(output);
|
$alert.html(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,20 +169,20 @@ $('#dismiss-cookie-alert').click(function(event){
|
|||||||
},
|
},
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(response){
|
success: function(response){
|
||||||
console.log(response);
|
console.log(response)
|
||||||
},
|
},
|
||||||
error: function(response){
|
error: function(response){
|
||||||
console.log(response);
|
console.log(response)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Script for Result Actions
|
// Script for Result Actions
|
||||||
$('.result-action-buttons').click(function(event){
|
$('.result-action-buttons').click(function(event){
|
||||||
|
|
||||||
var id = $(this).data('id');
|
var id = $(this).data('id')
|
||||||
|
|
||||||
if ($(this).data('result-action') == 'generate') {
|
if ($(this).data('result-action') == 'generate') {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@ -190,13 +192,13 @@ $('.result-action-buttons').click(function(event){
|
|||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
dataType: 'html',
|
dataType: 'html',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
var display_window = window.open();
|
var display_window = window.open()
|
||||||
display_window.document.write(response);
|
display_window.document.write(response)
|
||||||
},
|
},
|
||||||
error: function(response){
|
error: function(response){
|
||||||
error_response(response);
|
error_response(response)
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
var action = $(this).data('result-action')
|
var action = $(this).data('result-action')
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@ -206,23 +208,23 @@ $('.result-action-buttons').click(function(event){
|
|||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (action == 'delete') {
|
if (action == 'delete') {
|
||||||
window.location.href = '/admin/results/';
|
window.location.href = '/admin/results/'
|
||||||
} else window.location.reload();
|
} else window.location.reload()
|
||||||
},
|
},
|
||||||
error: function(response){
|
error: function(response){
|
||||||
error_response(response);
|
error_response(response)
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
// Script for Deleting Time Adjustment
|
// Script for Deleting Time Adjustment
|
||||||
$('.adjustment-delete').click(function(event){
|
$('.adjustment-delete').click(function(event){
|
||||||
|
|
||||||
var user_code = $(this).data('user_code');
|
var user_code = $(this).data('user_code')
|
||||||
var location = window.location.href;
|
var location = window.location.href
|
||||||
location = location.replace('#', '')
|
location = location.replace('#', '')
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@ -231,12 +233,19 @@ $('.adjustment-delete').click(function(event){
|
|||||||
data: JSON.stringify({'user_code': user_code}),
|
data: JSON.stringify({'user_code': user_code}),
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
window.location.reload();
|
window.location.reload()
|
||||||
},
|
},
|
||||||
error: function(response){
|
error: function(response){
|
||||||
error_response(response);
|
error_response(response)
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
|
// Detailed Results view questions
|
||||||
|
$('.view-full-questions').click(function(event) {
|
||||||
|
var dataset = $(this).data('dataset')
|
||||||
|
window.open(`/admin/view/${dataset}`, '_blank')
|
||||||
|
event.preventDefault()
|
||||||
|
})
|
@ -77,10 +77,13 @@
|
|||||||
<a href="{{ url_for('admin._users') }}" id="link-users" class="dropdown-item">Users</a>
|
<a href="{{ url_for('admin._users') }}" id="link-users" class="dropdown-item">Users</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url_for('admin._questions') }}" id="link-questions" class="dropdown-item">Question Datasets</a>
|
<a href="{{ url_for('admin._questions') }}" id="link-questions" class="dropdown-item">Manage Questions</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url_for('editor._editor') }}" id="link-editor" class="dropdown-item">Question Editor</a>
|
<a href="{{ url_for('view._view') }}" id="link-editor" class="dropdown-item">View Questions</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('editor._editor') }}" id="link-editor" class="dropdown-item">Edit Questions</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
@ -114,7 +114,7 @@
|
|||||||
{{ scores.scored }}
|
{{ scores.scored }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{scores.max}}
|
{{ scores.max }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -131,6 +131,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<div id="by-question-breakdown" class="accordion-collapse collapse" aria-labelledby="by-question" data-bs-parent="#results-breakdown">
|
<div id="by-question-breakdown" class="accordion-collapse collapse" aria-labelledby="by-question" data-bs-parent="#results-breakdown">
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
|
<a class="view-full-questions" data-dataset="{{ entry.test.dataset.id }}">View Questions</a>
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -146,7 +147,7 @@
|
|||||||
{% for question, answer in entry.answers.items() %}
|
{% for question, answer in entry.answers.items() %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{{ question }}
|
{{ question|int + 1 }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ answers[question|int][answer|int] }}
|
{{ answers[question|int][answer|int] }}
|
||||||
|
@ -57,28 +57,37 @@
|
|||||||
class="btn btn-primary edit-question-dataset"
|
class="btn btn-primary edit-question-dataset"
|
||||||
data-id="{{ element.id }}"
|
data-id="{{ element.id }}"
|
||||||
data-action="download"
|
data-action="download"
|
||||||
title="Download Dataset"
|
title="Download Questions"
|
||||||
>
|
>
|
||||||
<i class="bi bi-cloud-arrow-down-fill button-icon"></i>
|
<i class="bi bi-cloud-arrow-down-fill button-icon"></i>
|
||||||
</button>
|
</a>
|
||||||
|
<a
|
||||||
|
href="javascript:void(0)"
|
||||||
|
class="btn btn-primary view-question-dataset"
|
||||||
|
data-id="{{ element.id }}"
|
||||||
|
data-action="view"
|
||||||
|
title="View Questions"
|
||||||
|
>
|
||||||
|
<i class="bi bi-file-earmark-text-fill button-icon"></i>
|
||||||
|
</a>
|
||||||
<a
|
<a
|
||||||
href="javascript:void(0)"
|
href="javascript:void(0)"
|
||||||
class="btn btn-primary edit-question-dataset"
|
class="btn btn-primary edit-question-dataset"
|
||||||
data-id="{{ element.id }}"
|
data-id="{{ element.id }}"
|
||||||
data-action="edit"
|
data-action="edit"
|
||||||
title="Edit Dataset"
|
title="Edit Questions"
|
||||||
>
|
>
|
||||||
<i class="bi bi-file-earmark-text-fill button-icon"></i>
|
<i class="bi bi-pencil-fill button-icon"></i>
|
||||||
</button>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="javascript:void(0)"
|
href="javascript:void(0)"
|
||||||
class="btn btn-danger edit-question-dataset {% if element.default %}disabled{% endif %}"
|
class="btn btn-danger edit-question-dataset {% if element.default %}disabled{% endif %}"
|
||||||
data-id="{{ element.id }}"
|
data-id="{{ element.id }}"
|
||||||
data-action="delete"
|
data-action="delete"
|
||||||
title="Delete Dataset"
|
title="Delete Questions"
|
||||||
>
|
>
|
||||||
<i class="bi bi-file-earmark-excel-fill button-icon"></i>
|
<i class="bi bi-file-earmark-excel-fill button-icon"></i>
|
||||||
</button>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -2,7 +2,7 @@ from ..forms.admin import AddTimeAdjustment, CreateTest, CreateUser, DeleteUser,
|
|||||||
from ..models import Dataset, Entry, Test, User
|
from ..models import Dataset, Entry, Test, User
|
||||||
from ..tools.auth import disable_if_logged_in, require_account_creation
|
from ..tools.auth import disable_if_logged_in, require_account_creation
|
||||||
from ..tools.forms import get_dataset_choices, get_time_options, send_errors_to_client
|
from ..tools.forms import get_dataset_choices, get_time_options, send_errors_to_client
|
||||||
from ..tools.data import check_is_json, validate_json
|
from ..tools.data import check_dataset_exists, check_is_json, validate_json
|
||||||
from ..tools.test import answer_options, get_correct_answers
|
from ..tools.test import answer_options, get_correct_answers
|
||||||
|
|
||||||
from flask import abort, Blueprint, jsonify, render_template, redirect, request, send_file, session
|
from flask import abort, Blueprint, jsonify, render_template, redirect, request, send_file, session
|
||||||
@ -247,15 +247,12 @@ def _download(id:str):
|
|||||||
@admin.route('/tests/<string:filter>/', methods=['GET'])
|
@admin.route('/tests/<string:filter>/', methods=['GET'])
|
||||||
@admin.route('/tests/', methods=['GET'])
|
@admin.route('/tests/', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@check_dataset_exists
|
||||||
def _tests(filter:str=None):
|
def _tests(filter:str=None):
|
||||||
datasets = Dataset.query.all()
|
|
||||||
tests = None
|
tests = None
|
||||||
_tests = Test.query.all()
|
_tests = Test.query.all()
|
||||||
form = None
|
form = None
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
if not datasets:
|
|
||||||
flash('There are no available question datasets. Please upload a question dataset in order to set up an exam.', 'error')
|
|
||||||
return redirect(url_for('admin._questions'))
|
|
||||||
if filter not in ['create','active','scheduled','expired','all']: return redirect(url_for('admin._tests', filter='active'))
|
if filter not in ['create','active','scheduled','expired','all']: return redirect(url_for('admin._tests', filter='active'))
|
||||||
if filter == 'create':
|
if filter == 'create':
|
||||||
form = CreateTest()
|
form = CreateTest()
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
margin: 30pt auto;
|
margin: 30pt auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-panel {
|
.info-panel, .viewer-panel {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,4 +84,20 @@
|
|||||||
#alert-box {
|
#alert-box {
|
||||||
margin: 30px auto;
|
margin: 30px auto;
|
||||||
max-width: 460px;
|
max-width: 460px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
border: 2px solid black;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 10px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-body, .question-block {
|
||||||
|
padding: 0px 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
padding: 0px 2em;
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
@ -5,24 +5,69 @@ const id = $root.data('id')
|
|||||||
|
|
||||||
const $control_panel = $('.control-panel')
|
const $control_panel = $('.control-panel')
|
||||||
const $info_panel = $('.info-panel')
|
const $info_panel = $('.info-panel')
|
||||||
|
const $viewer_panel = $('.viewer-panel')
|
||||||
const $editor_panel = $('.editor-panel')
|
const $editor_panel = $('.editor-panel')
|
||||||
|
|
||||||
|
var toggle_info = false
|
||||||
|
var toggle_viewer = false
|
||||||
|
|
||||||
var element_index = 0
|
var element_index = 0
|
||||||
|
|
||||||
// Initialise Sortable and trigger renumbering on end of drag
|
// Initialise Sortable and trigger renumbering on end of drag
|
||||||
Sortable.create($root.get(0), {handle: '.move-handle', onEnd: function(evt) {renumber_blocks()}})
|
Sortable.create($root.get(0), {handle: '.move-handle', onEnd: function(evt) {renumber_blocks()}})
|
||||||
|
|
||||||
// Info Button Listener
|
// Info and Viewer Button Listener
|
||||||
$control_panel.find('button').click(function(event){
|
$control_panel.find('button').click(function(event){
|
||||||
if ($info_panel.is(":hidden")) {
|
var action = $(this).data('action');
|
||||||
$editor_panel.hide()
|
|
||||||
$info_panel.fadeIn()
|
if (action == 'info') {
|
||||||
$(this).addClass('active')
|
if ($info_panel.is(":hidden")) {
|
||||||
} else {
|
if ($viewer_panel.is(":visible")) {
|
||||||
$info_panel.hide()
|
toggle_viewer = true
|
||||||
$editor_panel.fadeIn()
|
$viewer_panel.hide()
|
||||||
$(this).removeClass('active')
|
}
|
||||||
|
$editor_panel.hide()
|
||||||
|
$info_panel.fadeIn()
|
||||||
|
$(window).scrollTop(0)
|
||||||
|
toggle_info = false
|
||||||
|
$(this).addClass('active')
|
||||||
|
} else {
|
||||||
|
$info_panel.hide()
|
||||||
|
if (toggle_viewer) {
|
||||||
|
render_viewer()
|
||||||
|
$(window).scrollTop(0)
|
||||||
|
toggle_viewer = false
|
||||||
|
} else {
|
||||||
|
$editor_panel.fadeIn()
|
||||||
|
$(window).scrollTop(0)
|
||||||
|
}
|
||||||
|
$(this).removeClass('active')
|
||||||
|
}
|
||||||
|
} else if (action == 'view') {
|
||||||
|
if ($viewer_panel.is(":hidden")) {
|
||||||
|
if ($info_panel.is(':visible')) {
|
||||||
|
toggle_info = true
|
||||||
|
$info_panel.hide()
|
||||||
|
}
|
||||||
|
$editor_panel.hide()
|
||||||
|
render_viewer()
|
||||||
|
$(window).scrollTop(0)
|
||||||
|
toggle_viewer = false
|
||||||
|
$(this).addClass('active')
|
||||||
|
} else {
|
||||||
|
$viewer_panel.hide()
|
||||||
|
if (toggle_info) {
|
||||||
|
$info_panel.fadeIn()
|
||||||
|
$(window).scrollTop(0)
|
||||||
|
toggle_info = false
|
||||||
|
} else {
|
||||||
|
$editor_panel.fadeIn()
|
||||||
|
$(window).scrollTop(0)
|
||||||
|
}
|
||||||
|
$(this).removeClass('active')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -496,4 +541,102 @@ $(window).on('load', function() {
|
|||||||
console.log(response)
|
console.log(response)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Viewer Render Function
|
||||||
|
function render_viewer() {
|
||||||
|
$viewer_panel.fadeIn()
|
||||||
|
$viewer_panel.empty()
|
||||||
|
var heading = document.createElement('h3')
|
||||||
|
heading.innerText = 'View Questions'
|
||||||
|
$viewer_panel.append(heading)
|
||||||
|
var data = parse_input()
|
||||||
|
var block
|
||||||
|
var obj
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
block = data[i]
|
||||||
|
obj = document.createElement('div')
|
||||||
|
obj.classList = 'block'
|
||||||
|
if (block['type'] == 'question') {
|
||||||
|
text = document.createElement('p')
|
||||||
|
text.innerHTML = `<strong>Question ${block['q_no'] + 1}.</strong> ${block['text']}`
|
||||||
|
obj.append(text)
|
||||||
|
question_body = document.createElement('div')
|
||||||
|
question_body.className ='question-body'
|
||||||
|
type = document.createElement('p')
|
||||||
|
type.innerHTML = `<strong>Question Type:</strong> ${block['q_type']}`
|
||||||
|
question_body.append(type)
|
||||||
|
options = document.createElement('p')
|
||||||
|
options.innerHTML = '<strong>Options:</strong>'
|
||||||
|
option_list = document.createElement('ul')
|
||||||
|
for (let _i = 0; _i < block['options'].length; _i++) {
|
||||||
|
option = document.createElement('li')
|
||||||
|
option.innerHTML = block['options'][_i]
|
||||||
|
if (block['correct'] == _i) {
|
||||||
|
option.innerHTML += ' <span class="badge rounded-pill bg-success">Correct</span>'
|
||||||
|
}
|
||||||
|
option_list.append(option)
|
||||||
|
}
|
||||||
|
options.append(option_list)
|
||||||
|
question_body.append(options)
|
||||||
|
tags = document.createElement('p')
|
||||||
|
tags.innerHTML = `<strong>Tags:</strong>`
|
||||||
|
tag_list = document.createElement('ul')
|
||||||
|
for (let _i = 0; _i < block['tags'].length; _i++) {
|
||||||
|
tag = document.createElement('li')
|
||||||
|
tag.innerHTML = block['tags'][_i]
|
||||||
|
tag_list.append(tag)
|
||||||
|
}
|
||||||
|
tags.append(tag_list)
|
||||||
|
question_body.append(tags)
|
||||||
|
obj.append(question_body)
|
||||||
|
} else if (block['type'] == 'block') {
|
||||||
|
meta = document.createElement('p')
|
||||||
|
meta.innerHTML = `<strong>Block ${i+1}.</strong> ${block['questions'].length} questions.`
|
||||||
|
obj.append(meta)
|
||||||
|
header = document.createElement('blockquote')
|
||||||
|
header.innerText = block['question_header']
|
||||||
|
obj.append(header)
|
||||||
|
var block_question = document.createElement('div')
|
||||||
|
var question
|
||||||
|
block_question.className = 'question-block'
|
||||||
|
for (let _i = 0; _i < block['questions'].length; _i++) {
|
||||||
|
question = block['questions'][_i]
|
||||||
|
text = document.createElement('p')
|
||||||
|
text.innerHTML = `<strong>Question ${question['q_no'] + 1}.</strong> ${question['text']}`
|
||||||
|
block_question.append(text)
|
||||||
|
question_body = document.createElement('div')
|
||||||
|
question_body.className ='question-body'
|
||||||
|
type = document.createElement('p')
|
||||||
|
type.innerHTML = `<strong>Question Type:</strong> ${question['q_type']}`
|
||||||
|
question_body.append(type)
|
||||||
|
options = document.createElement('p')
|
||||||
|
options.innerHTML = '<strong>Options:</strong>'
|
||||||
|
option_list = document.createElement('ul')
|
||||||
|
for (let __i = 0; __i < question['options'].length; __i++) {
|
||||||
|
option = document.createElement('li')
|
||||||
|
option.innerHTML = question['options'][__i]
|
||||||
|
if (question['correct'] == __i) {
|
||||||
|
option.innerHTML += ' <span class="badge rounded-pill bg-success">Correct</span>'
|
||||||
|
}
|
||||||
|
option_list.append(option)
|
||||||
|
}
|
||||||
|
options.append(option_list)
|
||||||
|
question_body.append(options)
|
||||||
|
tags = document.createElement('p')
|
||||||
|
tags.innerHTML = `<strong>Tags:</strong>`
|
||||||
|
tag_list = document.createElement('ul')
|
||||||
|
for (let __i = 0; __i < question['tags'].length; __i++) {
|
||||||
|
tag = document.createElement('li')
|
||||||
|
tag.innerHTML = question['tags'][__i]
|
||||||
|
tag_list.append(tag)
|
||||||
|
}
|
||||||
|
tags.append(tag_list)
|
||||||
|
question_body.append(tags)
|
||||||
|
block_question.append(question_body)
|
||||||
|
obj.append(block_question)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$viewer_panel.append(obj)
|
||||||
|
}
|
||||||
|
}
|
@ -57,7 +57,7 @@ function error_response(response) {
|
|||||||
`);
|
`);
|
||||||
} else if (response.responseJSON.error instanceof Array) {
|
} else if (response.responseJSON.error instanceof Array) {
|
||||||
var output = ''
|
var output = ''
|
||||||
for (var i = 0; i < response.responseJSON.error.length; i ++) {
|
for (let i = 0; i < response.responseJSON.error.length; i ++) {
|
||||||
output += `
|
output += `
|
||||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
||||||
|
@ -77,10 +77,13 @@
|
|||||||
<a href="{{ url_for('admin._users') }}" id="link-users" class="dropdown-item">Users</a>
|
<a href="{{ url_for('admin._users') }}" id="link-users" class="dropdown-item">Users</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url_for('admin._questions') }}" id="link-questions" class="dropdown-item">Question Datasets</a>
|
<a href="{{ url_for('admin._questions') }}" id="link-questions" class="dropdown-item">Manage Questions</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url_for('editor._editor') }}" id="link-editor" class="dropdown-item">Question Editor</a>
|
<a href="{{ url_for('view._view') }}" id="link-editor" class="dropdown-item">View Questions</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('editor._editor') }}" id="link-editor" class="dropdown-item">Edit Questions</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
@ -11,11 +11,12 @@
|
|||||||
<h1>Editor</h1>
|
<h1>Editor</h1>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
Use this console to edit the questions in this dataset. For more information on using the editor console, click on the the blue information button.
|
Use this console to edit the questions in this dataset. For more information on using the editor console, click on the the blue Information button. To preview the questions in the current dataset, click on the green View Questions button.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="container control-panel">
|
<div class="container control-panel">
|
||||||
<button class="btn btn-primary" aria-title="Infrmation" title="Information"><i class="bi bi-info-circle-fill"></i></button>
|
<button class="btn btn-primary" aria-title="Information" title="Information" data-action="info"><i class="bi bi-info-circle-fill"></i></button>
|
||||||
|
<button class="btn btn-success" aria-title="View Questions" title="View Questions" data-action="view"><i class="bi bi-book-fill"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="container info-panel">
|
<div class="container info-panel">
|
||||||
<h3>
|
<h3>
|
||||||
@ -77,9 +78,11 @@
|
|||||||
In order to show how many questions are remaining inside a block, e.g. to say ‘the next n questions are about a specific scenario’, use the placeholder <code><block_remaining_questions></code>.
|
In order to show how many questions are remaining inside a block, e.g. to say ‘the next n questions are about a specific scenario’, use the placeholder <code><block_remaining_questions></code>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="container viewer-panel">
|
||||||
|
</div>
|
||||||
<div class="container editor-panel">
|
<div class="container editor-panel">
|
||||||
<h3>
|
<h3>
|
||||||
Edit Dataset
|
Edit Questions
|
||||||
</h3>
|
</h3>
|
||||||
<div class="container dataset-metadata">
|
<div class="container dataset-metadata">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
@ -98,7 +101,7 @@
|
|||||||
<span class="input-group-text">Last Updated</span>
|
<span class="input-group-text">Last Updated</span>
|
||||||
<span class="form-control">
|
<span class="form-control">
|
||||||
{{ dataset.date.strftime('%d %b %Y %H:%M') }}
|
{{ dataset.date.strftime('%d %b %Y %H:%M') }}
|
||||||
</select>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<span class="input-group-text">
|
<span class="input-group-text">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="form-container">
|
<div class="form-container">
|
||||||
<form name="form-editor" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for(request.endpoint, **request.view_args) }}">
|
<form name="form-editor" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for(request.endpoint, **request.view_args) }}">
|
||||||
{% include "admin/components/server-alerts.html" %}
|
{% include "admin/components/server-alerts.html" %}
|
||||||
<h2 class="form">Dataset Editor</h2>
|
<h2 class="form">Edit Questions</h2>
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
<div class="form-select-input">
|
<div class="form-select-input">
|
||||||
{{ form.dataset(placeholder="Select Question Dataset") }}
|
{{ form.dataset(placeholder="Select Question Dataset") }}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from ..forms.admin import EditDataset
|
from ..forms.admin import EditDataset
|
||||||
from ..models import Dataset, User
|
from ..models import Dataset, User
|
||||||
from ..tools.forms import get_dataset_choices, send_errors_to_client
|
from ..tools.forms import get_dataset_choices, send_errors_to_client
|
||||||
|
from ..tools.data import check_dataset_exists
|
||||||
|
|
||||||
from flask import Blueprint, flash, jsonify, redirect, render_template, request
|
from flask import Blueprint, flash, jsonify, redirect, render_template, request
|
||||||
from flask.helpers import url_for
|
from flask.helpers import url_for
|
||||||
@ -27,6 +28,7 @@ def _editor():
|
|||||||
return render_template('/editor/index.html', form=form)
|
return render_template('/editor/index.html', form=form)
|
||||||
|
|
||||||
@editor.route('/<string:id>/')
|
@editor.route('/<string:id>/')
|
||||||
|
@check_dataset_exists
|
||||||
@login_required
|
@login_required
|
||||||
def _editor_console(id:str=None):
|
def _editor_console(id:str=None):
|
||||||
dataset = Dataset.query.filter_by(id=id).first()
|
dataset = Dataset.query.filter_by(id=id).first()
|
||||||
|
@ -1,142 +1,142 @@
|
|||||||
// Bind Listeners
|
// Bind Listeners
|
||||||
|
|
||||||
$("input[name='font-select']").change(function(){
|
$("input[name='font-select']").change(function(){
|
||||||
let $choice = $(this).val();
|
let $choice = $(this).val()
|
||||||
set_font($choice);
|
set_font($choice)
|
||||||
});
|
})
|
||||||
|
|
||||||
$("input[name='font-size']").change(function(){
|
$("input[name='font-size']").change(function(){
|
||||||
let $choice = $(this).val();
|
let $choice = $(this).val()
|
||||||
set_font_size($choice);
|
set_font_size($choice)
|
||||||
});
|
})
|
||||||
|
|
||||||
$("input[name='bg-select']").change(function(){
|
$("input[name='bg-select']").change(function(){
|
||||||
let $choice = $(this).val();
|
let $choice = $(this).val()
|
||||||
set_bg_colour($choice);
|
set_bg_colour($choice)
|
||||||
});
|
})
|
||||||
|
|
||||||
$(".bg-select-area").click(function(event){
|
$(".bg-select-area").click(function(event){
|
||||||
$(this).find("input[name='bg-select']").prop("checked", true).change();
|
$(this).find("input[name='bg-select']").prop("checked", true).change()
|
||||||
});
|
})
|
||||||
|
|
||||||
$("#btn-toggle-navigator").click(function(event){
|
$("#btn-toggle-navigator").click(function(event){
|
||||||
check_answered();
|
check_answered()
|
||||||
update_navigator();
|
update_navigator()
|
||||||
if ($quiz_navigator.is(":hidden")) {
|
if ($quiz_navigator.is(":hidden")) {
|
||||||
if ($quiz_settings.is(":visible")) {
|
if ($quiz_settings.is(":visible")) {
|
||||||
toggle_settings = true;
|
toggle_settings = true
|
||||||
$quiz_settings.fadeOut();
|
$quiz_settings.fadeOut()
|
||||||
}
|
}
|
||||||
$quiz_render.fadeOut();
|
$quiz_render.fadeOut()
|
||||||
$quiz_navigator.fadeIn();
|
$quiz_navigator.fadeIn()
|
||||||
$(".navigator-text").fadeIn();
|
$(".navigator-text").fadeIn()
|
||||||
$(".review-text").fadeOut();
|
$(".review-text").fadeOut()
|
||||||
toggle_navigator = false;
|
toggle_navigator = false
|
||||||
$(window).scrollTop(0);
|
$(window).scrollTop(0)
|
||||||
} else {
|
} else {
|
||||||
$quiz_navigator.fadeOut();
|
$quiz_navigator.fadeOut()
|
||||||
if (toggle_settings) {
|
if (toggle_settings) {
|
||||||
$quiz_settings.fadeIn();
|
$quiz_settings.fadeIn()
|
||||||
$(window).scrollTop(0);
|
$(window).scrollTop(0)
|
||||||
toggle_settings = false;
|
toggle_settings = false
|
||||||
} else {
|
} else {
|
||||||
$quiz_render.fadeIn();
|
$quiz_render.fadeIn()
|
||||||
$(window).scrollTop(0);
|
$(window).scrollTop(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
$("#btn-toggle-settings").click(function(event){
|
$("#btn-toggle-settings").click(function(event){
|
||||||
if (($quiz_settings).is(":hidden")) {
|
if (($quiz_settings).is(":hidden")) {
|
||||||
if ($quiz_navigator.is(":visible")) {
|
if ($quiz_navigator.is(":visible")) {
|
||||||
toggle_navigator = true;
|
toggle_navigator = true
|
||||||
$quiz_navigator.fadeOut();
|
$quiz_navigator.fadeOut()
|
||||||
}
|
}
|
||||||
$quiz_render.fadeOut();
|
$quiz_render.fadeOut()
|
||||||
$quiz_settings.fadeIn();
|
$quiz_settings.fadeIn()
|
||||||
$(window).scrollTop(0);
|
$(window).scrollTop(0)
|
||||||
toggle_settings = false;
|
toggle_settings = false
|
||||||
} else {
|
} else {
|
||||||
$quiz_settings.fadeOut();
|
$quiz_settings.fadeOut()
|
||||||
if (toggle_navigator) {
|
if (toggle_navigator) {
|
||||||
$quiz_navigator.fadeIn();
|
$quiz_navigator.fadeIn()
|
||||||
toggle_navigator = false;
|
toggle_navigator = false
|
||||||
$(window).scrollTop(0);
|
$(window).scrollTop(0)
|
||||||
} else {
|
} else {
|
||||||
$quiz_render.fadeIn();
|
$quiz_render.fadeIn()
|
||||||
$(window).scrollTop(0);
|
$(window).scrollTop(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
$(".btn-quiz-return").click(function(event){
|
$(".btn-quiz-return").click(function(event){
|
||||||
$quiz_navigator.fadeOut();
|
$quiz_navigator.fadeOut()
|
||||||
$quiz_settings.fadeOut();
|
$quiz_settings.fadeOut()
|
||||||
$quiz_render.fadeIn();
|
$quiz_render.fadeIn()
|
||||||
$(window).scrollTop(0);
|
$(window).scrollTop(0)
|
||||||
toggle_settings = false;
|
toggle_settings = false
|
||||||
toggle_navigator = false;
|
toggle_navigator = false
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
$(".btn-dummy").click(function(event){
|
$(".btn-dummy").click(function(event){
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
$("#navigator-container").on("click", ".q-navigator-button", function(event){
|
$("#navigator-container").on("click", ".q-navigator-button", function(event){
|
||||||
check_answered();
|
check_answered()
|
||||||
update_navigator();
|
update_navigator()
|
||||||
current_question = parseInt($(this).prop("name"));
|
current_question = parseInt($(this).prop("name"))
|
||||||
$quiz_navigator.fadeOut();
|
$quiz_navigator.fadeOut()
|
||||||
$quiz_render.fadeIn();
|
$quiz_render.fadeIn()
|
||||||
$question_title.focus();
|
$question_title.focus()
|
||||||
$(window).scrollTop(0);
|
$(window).scrollTop(0)
|
||||||
toggle_navigator = false;
|
toggle_navigator = false
|
||||||
toggle_settings = false;
|
toggle_settings = false
|
||||||
render_question();
|
render_question()
|
||||||
check_flag();
|
check_flag()
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
$(".q-question-nav").click(function(event){
|
$(".q-question-nav").click(function(event){
|
||||||
check_answered();
|
check_answered()
|
||||||
update_navigator();
|
update_navigator()
|
||||||
if ($(this).prop("id") == "q-nav-next") {
|
if ($(this).prop("id") == "q-nav-next") {
|
||||||
if (current_question < questions.length) {
|
if (current_question < questions.length) {
|
||||||
current_question ++;
|
current_question ++
|
||||||
}
|
}
|
||||||
} else if ($(this).prop("id") == "q-nav-prev") {
|
} else if ($(this).prop("id") == "q-nav-prev") {
|
||||||
if (current_question > 0) {
|
if (current_question > 0) {
|
||||||
current_question --;
|
current_question --
|
||||||
}
|
}
|
||||||
} else if ($(this).hasClass("q-navigator-button")) {
|
} else if ($(this).hasClass("q-navigator-button")) {
|
||||||
current_question = $(this).prop("name");
|
current_question = $(this).prop("name")
|
||||||
$quiz_render.fadeIn();
|
$quiz_render.fadeIn()
|
||||||
$quiz_navigator.fadeOut();
|
$quiz_navigator.fadeOut()
|
||||||
toggle_navigator = false;
|
toggle_navigator = false
|
||||||
toggle_settings = false;
|
toggle_settings = false
|
||||||
}
|
}
|
||||||
render_question();
|
render_question()
|
||||||
check_flag();
|
check_flag()
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
$("#q-nav-flag").click(function(event){
|
$("#q-nav-flag").click(function(event){
|
||||||
if (question_status[current_question] != 1) {
|
if (question_status[current_question] != 1) {
|
||||||
question_status[current_question] = 1;
|
question_status[current_question] = 1
|
||||||
$(this).removeClass().addClass("btn btn-warning");
|
$(this).removeClass().addClass("btn btn-warning")
|
||||||
$(this).prop("title", "Question Flagged for revision. Click to un-flag.");
|
$(this).prop("title", "Question Flagged for revision. Click to un-flag.")
|
||||||
} else {
|
} else {
|
||||||
question_status[current_question] = 0;
|
question_status[current_question] = 0
|
||||||
$(this).removeClass().addClass("btn btn-secondary");
|
$(this).removeClass().addClass("btn btn-secondary")
|
||||||
$(this).prop("title", "Question Un-Flagged. Click to flag for revision.");
|
$(this).prop("title", "Question Un-Flagged. Click to flag for revision.")
|
||||||
}
|
}
|
||||||
window.localStorage.setItem('question_status', JSON.stringify(question_status));
|
window.localStorage.setItem('question_status', JSON.stringify(question_status))
|
||||||
update_navigator();
|
update_navigator()
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
$("#btn-start-quiz").click(function(event){
|
$("#btn-start-quiz").click(function(event){
|
||||||
|
|
||||||
@ -146,29 +146,29 @@ $("#btn-start-quiz").click(function(event){
|
|||||||
data: JSON.stringify({'id': id}),
|
data: JSON.stringify({'id': id}),
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
$("#btn-start-quiz").fadeOut();
|
$("#btn-start-quiz").fadeOut()
|
||||||
$(".btn-quiz-return").fadeIn();
|
$(".btn-quiz-return").fadeIn()
|
||||||
$(".quiz-console").fadeIn();
|
$(".quiz-console").fadeIn()
|
||||||
$("#quiz-settings").fadeOut();
|
$("#quiz-settings").fadeOut()
|
||||||
$("#quiz-navigator").fadeOut();
|
$("#quiz-navigator").fadeOut()
|
||||||
$(".quiz-start-text").fadeOut();
|
$(".quiz-start-text").fadeOut()
|
||||||
time_limit = response.time_limit;
|
time_limit = response.time_limit
|
||||||
start_time = response.start_time;
|
start_time = response.start_time
|
||||||
questions = response.questions;
|
questions = response.questions
|
||||||
total_questions = questions.length;
|
total_questions = questions.length
|
||||||
window.localStorage.setItem('questions', JSON.stringify(questions));
|
window.localStorage.setItem('questions', JSON.stringify(questions))
|
||||||
window.localStorage.setItem('start_time', JSON.stringify(start_time));
|
window.localStorage.setItem('start_time', JSON.stringify(start_time))
|
||||||
window.localStorage.setItem('time_limit', JSON.stringify(time_limit));
|
window.localStorage.setItem('time_limit', JSON.stringify(time_limit))
|
||||||
render_question();
|
render_question()
|
||||||
build_navigator();
|
build_navigator()
|
||||||
check_flag();
|
check_flag()
|
||||||
if (time_limit != 'null' && time_limit != null) {
|
if (time_limit != 'null' && time_limit != null) {
|
||||||
$("#q-timer-widget").fadeIn();
|
$("#q-timer-widget").fadeIn()
|
||||||
time_remaining = get_time_remaining();
|
time_remaining = get_time_remaining()
|
||||||
clock = setInterval(timer, 1000);
|
clock = setInterval(timer, 1000)
|
||||||
}
|
}
|
||||||
if (response.time_adjustment > 0) {
|
if (response.time_adjustment > 0) {
|
||||||
const $alert = $("#alert-box");
|
const $alert = $("#alert-box")
|
||||||
$alert.html(
|
$alert.html(
|
||||||
`<div class="alert alert-primary alert-dismissible fade show" role="alert">
|
`<div class="alert alert-primary alert-dismissible fade show" role="alert">
|
||||||
<i class="bi bi-exclamation-triangle-fill" title="Alert"></i>
|
<i class="bi bi-exclamation-triangle-fill" title="Alert"></i>
|
||||||
@ -176,50 +176,50 @@ $("#btn-start-quiz").click(function(event){
|
|||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
);
|
)
|
||||||
$alert.focus();
|
$alert.focus()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(response) {
|
error: function(response) {
|
||||||
error_response(response);
|
error_response(response)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
$("#quiz-question-options").on("change", ".quiz-option", function(event){
|
$("#quiz-question-options").on("change", ".quiz-option", function(event){
|
||||||
$name = parseInt($(this).prop("name"));
|
$name = parseInt($(this).prop("name"))
|
||||||
$value = $(this).prop("value");
|
$value = $(this).prop("value")
|
||||||
answers[$name] = $value;
|
answers[$name] = $value
|
||||||
window.localStorage.setItem('answers', JSON.stringify(answers));
|
window.localStorage.setItem('answers', JSON.stringify(answers))
|
||||||
});
|
})
|
||||||
|
|
||||||
$("#q-review-answers").click(function(event){
|
$("#q-review-answers").click(function(event){
|
||||||
check_answered();
|
check_answered()
|
||||||
update_navigator();
|
update_navigator()
|
||||||
if ($quiz_navigator.is(":hidden")) {
|
if ($quiz_navigator.is(":hidden")) {
|
||||||
if ($quiz_settings.is(":visible")) {
|
if ($quiz_settings.is(":visible")) {
|
||||||
toggle_settings = true;
|
toggle_settings = true
|
||||||
$quiz_settings.fadeOut();
|
$quiz_settings.fadeOut()
|
||||||
}
|
}
|
||||||
$quiz_render.fadeOut();
|
$quiz_render.fadeOut()
|
||||||
$quiz_navigator.fadeIn();
|
$quiz_navigator.fadeIn()
|
||||||
$(".navigator-text").fadeOut();
|
$(".navigator-text").fadeOut()
|
||||||
$(".review-text").fadeIn();
|
$(".review-text").fadeIn()
|
||||||
toggle_navigator = false;
|
toggle_navigator = false
|
||||||
$(window).scrollTop(0);
|
$(window).scrollTop(0)
|
||||||
} else {
|
} else {
|
||||||
$quiz_navigator.fadeOut();
|
$quiz_navigator.fadeOut()
|
||||||
if (toggle_settings) {
|
if (toggle_settings) {
|
||||||
$quiz_settings.fadeIn();
|
$quiz_settings.fadeIn()
|
||||||
toggle_settings = false;
|
toggle_settings = false
|
||||||
} else {
|
} else {
|
||||||
$quiz_render.fadeIn();
|
$quiz_render.fadeIn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
$(".quiz-button-submit").click(function(event){
|
$(".quiz-button-submit").click(function(event){
|
||||||
let submission = {
|
let submission = {
|
||||||
@ -233,16 +233,16 @@ $(".quiz-button-submit").click(function(event){
|
|||||||
data: JSON.stringify(submission),
|
data: JSON.stringify(submission),
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
window.localStorage.clear();
|
window.localStorage.clear()
|
||||||
window.location.href = `/result/`;
|
window.location.href = `/result/`
|
||||||
},
|
},
|
||||||
error: function(response) {
|
error: function(response) {
|
||||||
error_response(response);
|
error_response(response)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
|
|
||||||
@ -251,17 +251,17 @@ function set_font(value = 'osdefault') {
|
|||||||
|
|
||||||
for (let i = 0; i < font_styles.length; i ++) {
|
for (let i = 0; i < font_styles.length; i ++) {
|
||||||
if (font_styles[i] != value) {
|
if (font_styles[i] != value) {
|
||||||
$("body").removeClass( `q-f-${font_styles[i]}` );
|
$("body").removeClass( `q-f-${font_styles[i]}` )
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
if (value != 'osdefault') {
|
if (value != 'osdefault') {
|
||||||
$("body").addClass(`q-f-${value}`);
|
$("body").addClass(`q-f-${value}`)
|
||||||
};
|
}
|
||||||
|
|
||||||
display_settings['font-select'] = value;
|
display_settings['font-select'] = value
|
||||||
window.localStorage.setItem('display_settings', JSON.stringify(display_settings));
|
window.localStorage.setItem('display_settings', JSON.stringify(display_settings))
|
||||||
$('input[name="font-select"][value="' + value + '"]').prop('checked', true);
|
$('input[name="font-select"][value="' + value + '"]').prop('checked', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_font_size(value = '14pt') {
|
function set_font_size(value = '14pt') {
|
||||||
@ -269,17 +269,17 @@ function set_font_size(value = '14pt') {
|
|||||||
|
|
||||||
for (let i = 0; i < font_sizes.length; i ++) {
|
for (let i = 0; i < font_sizes.length; i ++) {
|
||||||
if (font_sizes[i] != value) {
|
if (font_sizes[i] != value) {
|
||||||
$("body").removeClass( `q-f-${font_sizes[i]}` );
|
$("body").removeClass( `q-f-${font_sizes[i]}` )
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
if (value != '14pt') {
|
if (value != '14pt') {
|
||||||
$("body").addClass(`q-f-${value}`);
|
$("body").addClass(`q-f-${value}`)
|
||||||
};
|
}
|
||||||
|
|
||||||
display_settings['font-size'] = value;
|
display_settings['font-size'] = value
|
||||||
window.localStorage.setItem('display_settings', JSON.stringify(display_settings));
|
window.localStorage.setItem('display_settings', JSON.stringify(display_settings))
|
||||||
$('input[name="font-size"][value="' + value + '"]').prop('checked', true);
|
$('input[name="font-size"][value="' + value + '"]').prop('checked', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_bg_colour(value = 'bg-light') {
|
function set_bg_colour(value = 'bg-light') {
|
||||||
@ -287,34 +287,34 @@ function set_bg_colour(value = 'bg-light') {
|
|||||||
|
|
||||||
for (let i = 0; i < backgrounds.length; i ++) {
|
for (let i = 0; i < backgrounds.length; i ++) {
|
||||||
if (backgrounds[i] != value) {
|
if (backgrounds[i] != value) {
|
||||||
$("body").removeClass(backgrounds[i]);
|
$("body").removeClass(backgrounds[i])
|
||||||
if (backgrounds[i] == 'bg-dark') {
|
if (backgrounds[i] == 'bg-dark') {
|
||||||
$("body").removeClass('text-light');
|
$("body").removeClass('text-light')
|
||||||
};
|
}
|
||||||
if (backgrounds[i] == 'alert-primary' || backgrounds[i] == 'alert-secondary' || backgrounds[i] == 'alert-dark') {
|
if (backgrounds[i] == 'alert-primary' || backgrounds[i] == 'alert-secondary' || backgrounds[i] == 'alert-dark') {
|
||||||
$("body").removeClass('text-dark');
|
$("body").removeClass('text-dark')
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
$("body").addClass(value);
|
$("body").addClass(value)
|
||||||
if (value == 'bg-dark') {
|
if (value == 'bg-dark') {
|
||||||
$("body").addClass('text-light');
|
$("body").addClass('text-light')
|
||||||
};
|
}
|
||||||
if (value == 'alert-primary' || value == 'alert-secondary' || value == 'alert-dark') {
|
if (value == 'alert-primary' || value == 'alert-secondary' || value == 'alert-dark') {
|
||||||
$("body").addClass('text-dark');
|
$("body").addClass('text-dark')
|
||||||
};
|
}
|
||||||
|
|
||||||
display_settings['bg-select'] = value;
|
display_settings['bg-select'] = value
|
||||||
window.localStorage.setItem('display_settings', JSON.stringify(display_settings));
|
window.localStorage.setItem('display_settings', JSON.stringify(display_settings))
|
||||||
$('input[name="bg-select"][value="' + value + '"]').prop('checked', true);
|
$('input[name="bg-select"][value="' + value + '"]').prop('checked', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_settings_from_storage() {
|
function get_settings_from_storage() {
|
||||||
let display_settings = window.localStorage.getItem('display_settings')
|
let display_settings = window.localStorage.getItem('display_settings')
|
||||||
if (display_settings != null) {
|
if (display_settings != null) {
|
||||||
return JSON.parse(display_settings);
|
return JSON.parse(display_settings)
|
||||||
};
|
}
|
||||||
return {
|
return {
|
||||||
'font-select': 'osdefault',
|
'font-select': 'osdefault',
|
||||||
'font-size': '14pt',
|
'font-size': '14pt',
|
||||||
@ -323,133 +323,133 @@ function get_settings_from_storage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function apply_settings(settings) {
|
function apply_settings(settings) {
|
||||||
set_font(settings['font-select']);
|
set_font(settings['font-select'])
|
||||||
set_font_size(settings['font-size']);
|
set_font_size(settings['font-size'])
|
||||||
set_bg_colour(settings['bg-select']);
|
set_bg_colour(settings['bg-select'])
|
||||||
}
|
}
|
||||||
|
|
||||||
function render_question() {
|
function render_question() {
|
||||||
if (current_question == 0) {
|
if (current_question == 0) {
|
||||||
$nav_prev.addClass('disabled');
|
$nav_prev.addClass('disabled')
|
||||||
}
|
}
|
||||||
if (current_question == questions.length - 1) {
|
if (current_question == questions.length - 1) {
|
||||||
$nav_next.addClass('disabled');
|
$nav_next.addClass('disabled')
|
||||||
}
|
}
|
||||||
if ($nav_prev.hasClass('disabled') && current_question > 0) {
|
if ($nav_prev.hasClass('disabled') && current_question > 0) {
|
||||||
$nav_prev.removeClass('disabled');
|
$nav_prev.removeClass('disabled')
|
||||||
}
|
}
|
||||||
if ($nav_next.hasClass('disabled') && current_question < questions.length - 1) {
|
if ($nav_next.hasClass('disabled') && current_question < questions.length - 1) {
|
||||||
$nav_next.removeClass('disabled');
|
$nav_next.removeClass('disabled')
|
||||||
}
|
}
|
||||||
var question = questions[current_question];
|
var question = questions[current_question]
|
||||||
let header_text = question.question_header;
|
let header_text = question.question_header
|
||||||
var block_length = 0;
|
var block_length = 0
|
||||||
if ('block_length' in question) {
|
if ('block_length' in question) {
|
||||||
block_length = question['block_length'];
|
block_length = question['block_length']
|
||||||
};
|
}
|
||||||
var block_q_no = 0;
|
var block_q_no = 0
|
||||||
if ('block_q_no' in question) {
|
if ('block_q_no' in question) {
|
||||||
block_q_no = question['block_q_no'];
|
block_q_no = question['block_q_no']
|
||||||
}
|
}
|
||||||
let remaining_qs = (block_length - block_q_no).toString();
|
let remaining_qs = (block_length - block_q_no).toString()
|
||||||
if (block_length - block_q_no > 1) {
|
if (block_length - block_q_no > 1) {
|
||||||
remaining_qs += ' questions';
|
remaining_qs += ' questions'
|
||||||
} else {
|
} else {
|
||||||
remaining_qs += ' question';
|
remaining_qs += ' question'
|
||||||
}
|
}
|
||||||
header_text = header_text.replace('<block_remaining_questions>', remaining_qs);
|
header_text = header_text.replace('<block_remaining_questions>', remaining_qs)
|
||||||
$question_header.html(header_text);
|
$question_header.html(header_text)
|
||||||
$question_text.html(question.text);
|
$question_text.html(question.text)
|
||||||
$question_title.html(`Question ${current_question + 1} of ${ questions.length }.`);
|
$question_title.html(`Question ${current_question + 1} of ${ questions.length }.`)
|
||||||
|
|
||||||
var q_no = question['q_no'];
|
var q_no = question['q_no']
|
||||||
var options = question.options;
|
var options = question.options
|
||||||
var options_output = '';
|
var options_output = ''
|
||||||
for (let i = 0; i < options.length; i ++) {
|
for (let i = 0; i < options.length; i ++) {
|
||||||
var add_checked = ''
|
var add_checked = ''
|
||||||
if (q_no in answers) {
|
if (q_no in answers) {
|
||||||
if (answers[q_no] == options[i][0]) {
|
if (answers[q_no] == options[i][0]) {
|
||||||
add_checked = 'checked';
|
add_checked = 'checked'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
options_output += `<div class="form-check">
|
options_output += `<div class="form-check">
|
||||||
<input type="radio" class="form-check-input quiz-option" id="q${current_question}-${i}" name="${q_no}" value="${options[i][0]}" ${add_checked}>
|
<input type="radio" class="form-check-input quiz-option" id="q${current_question}-${i}" name="${q_no}" value="${options[i][0]}" ${add_checked}>
|
||||||
<label for="q${current_question}-${i}" class="form-check-label">${options[i][1]}</label>
|
<label for="q${current_question}-${i}" class="form-check-label">${options[i][1]}</label>
|
||||||
</div>`;
|
</div>`
|
||||||
}
|
}
|
||||||
$question_options.html(options_output);
|
$question_options.html(options_output)
|
||||||
let skipped = count_questions(-1);
|
let skipped = count_questions(-1)
|
||||||
let answered = count_questions(2);
|
let answered = count_questions(2)
|
||||||
let flagged = count_questions(1);
|
let flagged = count_questions(1)
|
||||||
|
|
||||||
$progress_skipped.prop('title', `Skipped: ${skipped}`);
|
$progress_skipped.prop('title', `Skipped: ${skipped}`)
|
||||||
$progress_skipped.prop('aria-valuenow', skipped);
|
$progress_skipped.prop('aria-valuenow', skipped)
|
||||||
$progress_skipped.css('width', `${skipped}%`);
|
$progress_skipped.css('width', `${skipped}%`)
|
||||||
$skipped_count.text(`Skipped: ${skipped}`);
|
$skipped_count.text(`Skipped: ${skipped}`)
|
||||||
if (skipped < 1) {
|
if (skipped < 1) {
|
||||||
$skipped_count.fadeOut()
|
$skipped_count.fadeOut()
|
||||||
} else {
|
} else {
|
||||||
$skipped_count.fadeIn()
|
$skipped_count.fadeIn()
|
||||||
}
|
}
|
||||||
|
|
||||||
$progress_flagged.prop('title', `Flagged: ${flagged}`);
|
$progress_flagged.prop('title', `Flagged: ${flagged}`)
|
||||||
$progress_flagged.prop('aria-valuenow', flagged);
|
$progress_flagged.prop('aria-valuenow', flagged)
|
||||||
$progress_flagged.css('width', `${flagged}%`);
|
$progress_flagged.css('width', `${flagged}%`)
|
||||||
$flagged_count.text(`Flagged: ${flagged}`);
|
$flagged_count.text(`Flagged: ${flagged}`)
|
||||||
if (flagged < 1) {
|
if (flagged < 1) {
|
||||||
$flagged_count.fadeOut()
|
$flagged_count.fadeOut()
|
||||||
} else {
|
} else {
|
||||||
$flagged_count.fadeIn()
|
$flagged_count.fadeIn()
|
||||||
}
|
}
|
||||||
|
|
||||||
$progress_answered.prop('title', `Answered: ${answered}`);
|
$progress_answered.prop('title', `Answered: ${answered}`)
|
||||||
$progress_answered.prop('aria-valuenow', answered);
|
$progress_answered.prop('aria-valuenow', answered)
|
||||||
$progress_answered.css('width', `${answered}%`);
|
$progress_answered.css('width', `${answered}%`)
|
||||||
$answered_count.text(`Answered: ${answered}`);
|
$answered_count.text(`Answered: ${answered}`)
|
||||||
if (answered < 1) {
|
if (answered < 1) {
|
||||||
$answered_count.fadeOut()
|
$answered_count.fadeOut()
|
||||||
} else {
|
} else {
|
||||||
$answered_count.fadeIn()
|
$answered_count.fadeIn()
|
||||||
}
|
}
|
||||||
|
|
||||||
$question_title.focus();
|
$question_title.focus()
|
||||||
$(window).scrollTop(0);
|
$(window).scrollTop(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function check_answered() {
|
function check_answered() {
|
||||||
var question = questions[current_question];
|
var question = questions[current_question]
|
||||||
var name = question.q_no;
|
var name = question.q_no
|
||||||
if (question_status[current_question] == 0 || question_status[current_question] == -1) {
|
if (question_status[current_question] == 0 || question_status[current_question] == -1) {
|
||||||
if (!$(`input[name='${name}']:checked`).val()) {
|
if (!$(`input[name='${name}']:checked`).val()) {
|
||||||
question_status[current_question] = -1;
|
question_status[current_question] = -1
|
||||||
} else {
|
} else {
|
||||||
question_status[current_question] = 2;
|
question_status[current_question] = 2
|
||||||
}
|
}
|
||||||
window.localStorage.setItem('question_status', JSON.stringify(question_status));
|
window.localStorage.setItem('question_status', JSON.stringify(question_status))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function check_flag() {
|
function check_flag() {
|
||||||
if (!(current_question in question_status)) {
|
if (!(current_question in question_status)) {
|
||||||
question_status[current_question] = 0;
|
question_status[current_question] = 0
|
||||||
window.localStorage.setItem('question_status', JSON.stringify(question_status));
|
window.localStorage.setItem('question_status', JSON.stringify(question_status))
|
||||||
}
|
}
|
||||||
switch (question_status[current_question]) {
|
switch (question_status[current_question]) {
|
||||||
case -1:
|
case -1:
|
||||||
$nav_flag.removeClass().addClass('btn btn-danger progress-bar-striped');
|
$nav_flag.removeClass().addClass('btn btn-danger progress-bar-striped')
|
||||||
$nav_flag.prop("title", "Question Incomplete. Click to flag for revision.");
|
$nav_flag.prop("title", "Question Incomplete. Click to flag for revision.")
|
||||||
break;
|
break
|
||||||
case 1:
|
case 1:
|
||||||
$nav_flag.removeClass().addClass('btn btn-warning');
|
$nav_flag.removeClass().addClass('btn btn-warning')
|
||||||
$nav_flag.prop("title", "Question Flagged for revision. Click to un-flag.");
|
$nav_flag.prop("title", "Question Flagged for revision. Click to un-flag.")
|
||||||
break;
|
break
|
||||||
case 2:
|
case 2:
|
||||||
$nav_flag.removeClass().addClass('btn btn-success');
|
$nav_flag.removeClass().addClass('btn btn-success')
|
||||||
$nav_flag.prop("title", "Question Answered. Click to flag for revision.");
|
$nav_flag.prop("title", "Question Answered. Click to flag for revision.")
|
||||||
break;
|
break
|
||||||
default:
|
default:
|
||||||
$nav_flag.removeClass().addClass('btn btn-secondary');
|
$nav_flag.removeClass().addClass('btn btn-secondary')
|
||||||
$nav_flag.prop("title", "Question Un-Flagged. Click to flag for revision.");
|
$nav_flag.prop("title", "Question Un-Flagged. Click to flag for revision.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,31 +457,31 @@ function build_navigator() {
|
|||||||
$nav_container.html('')
|
$nav_container.html('')
|
||||||
var output = ''
|
var output = ''
|
||||||
for (let i = 0; i < questions.length; i ++) {
|
for (let i = 0; i < questions.length; i ++) {
|
||||||
let add_class, add_href, add_status = '';
|
let add_class, add_href, add_status = ''
|
||||||
switch (question_status[i]) {
|
switch (question_status[i]) {
|
||||||
case -1:
|
case -1:
|
||||||
add_class = 'btn-danger progress-bar-striped';
|
add_class = 'btn-danger progress-bar-striped'
|
||||||
add_href = 'href="#"';
|
add_href = 'href="#"'
|
||||||
add_status = 'Incomplete';
|
add_status = 'Incomplete'
|
||||||
break;
|
break
|
||||||
case 1:
|
case 1:
|
||||||
add_class = 'btn-warning';
|
add_class = 'btn-warning'
|
||||||
add_href = 'href="#"';
|
add_href = 'href="#"'
|
||||||
add_status = 'Flagged';
|
add_status = 'Flagged'
|
||||||
break;
|
break
|
||||||
case 2:
|
case 2:
|
||||||
add_class = 'btn-success';
|
add_class = 'btn-success'
|
||||||
add_href = 'href="#"';
|
add_href = 'href="#"'
|
||||||
add_status = 'Answered';
|
add_status = 'Answered'
|
||||||
break;
|
break
|
||||||
default:
|
default:
|
||||||
add_class = 'btn-secondary disabled';
|
add_class = 'btn-secondary disabled'
|
||||||
add_href = '';
|
add_href = ''
|
||||||
add_status = 'Unseen';
|
add_status = 'Unseen'
|
||||||
}
|
}
|
||||||
output += `<a ${add_href} class="q-navigator-button btn ${add_class}" name=${i} title="Question ${i+1}: ${add_status}">Q${i + 1}</a>`;
|
output += `<a ${add_href} class="q-navigator-button btn ${add_class}" name=${i} title="Question ${i+1}: ${add_status}">Q${i + 1}</a>`
|
||||||
}
|
}
|
||||||
$nav_container.html(output);
|
$nav_container.html(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_navigator() {
|
function update_navigator() {
|
||||||
@ -489,162 +489,162 @@ function update_navigator() {
|
|||||||
if (current_question in question_status) {
|
if (current_question in question_status) {
|
||||||
switch (question_status[current_question]) {
|
switch (question_status[current_question]) {
|
||||||
case -1:
|
case -1:
|
||||||
button.removeClass().addClass("q-navigator-button btn btn-danger progress-bar-striped");
|
button.removeClass().addClass("q-navigator-button btn btn-danger progress-bar-striped")
|
||||||
button.prop("title", `Question ${current_question + 1}: Incomplete`);
|
button.prop("title", `Question ${current_question + 1}: Incomplete`)
|
||||||
break;
|
break
|
||||||
case 1:
|
case 1:
|
||||||
button.removeClass().addClass("q-navigator-button btn btn-warning");
|
button.removeClass().addClass("q-navigator-button btn btn-warning")
|
||||||
button.prop("title", `Question ${current_question + 1}: Flagged`);
|
button.prop("title", `Question ${current_question + 1}: Flagged`)
|
||||||
break;
|
break
|
||||||
case 2:
|
case 2:
|
||||||
button.removeClass().addClass("q-navigator-button btn btn-success");
|
button.removeClass().addClass("q-navigator-button btn btn-success")
|
||||||
button.prop("title", `Question ${current_question + 1}: Answered`);
|
button.prop("title", `Question ${current_question + 1}: Answered`)
|
||||||
break;
|
break
|
||||||
default:
|
default:
|
||||||
button.removeClass().addClass("q-navigator-button btn btn-secondary disabled");
|
button.removeClass().addClass("q-navigator-button btn btn-secondary disabled")
|
||||||
button.prop("title", `Question ${current_question + 1}: Unseen`);
|
button.prop("title", `Question ${current_question + 1}: Unseen`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
$("#btn-start-quiz").fadeOut();
|
$("#btn-start-quiz").fadeOut()
|
||||||
$(".btn-quiz-return").fadeIn();
|
$(".btn-quiz-return").fadeIn()
|
||||||
$(".quiz-console").fadeIn();
|
$(".quiz-console").fadeIn()
|
||||||
$("#quiz-settings").fadeOut();
|
$("#quiz-settings").fadeOut()
|
||||||
$("#quiz-navigator").fadeOut();
|
$("#quiz-navigator").fadeOut()
|
||||||
$(".quiz-start-text").fadeOut();
|
$(".quiz-start-text").fadeOut()
|
||||||
|
|
||||||
questions = JSON.parse(window.localStorage.getItem('questions'));
|
questions = JSON.parse(window.localStorage.getItem('questions'))
|
||||||
total_questions = questions.length;
|
total_questions = questions.length
|
||||||
start_time = window.localStorage.getItem('start_time');
|
start_time = window.localStorage.getItem('start_time')
|
||||||
time_limit = window.localStorage.getItem('time_limit');
|
time_limit = window.localStorage.getItem('time_limit')
|
||||||
|
|
||||||
let get_answers = window.localStorage.getItem('answers');
|
let get_answers = window.localStorage.getItem('answers')
|
||||||
if (get_answers != null) {
|
if (get_answers != null) {
|
||||||
answers = JSON.parse(get_answers);
|
answers = JSON.parse(get_answers)
|
||||||
}
|
}
|
||||||
|
|
||||||
let get_status = window.localStorage.getItem('question_status');
|
let get_status = window.localStorage.getItem('question_status')
|
||||||
if (get_status != null) {
|
if (get_status != null) {
|
||||||
question_status = JSON.parse(get_status);
|
question_status = JSON.parse(get_status)
|
||||||
}
|
}
|
||||||
|
|
||||||
render_question();
|
render_question()
|
||||||
build_navigator();
|
build_navigator()
|
||||||
check_flag();
|
check_flag()
|
||||||
if (time_limit != 'null' && time_limit != null) {
|
if (time_limit != 'null' && time_limit != null) {
|
||||||
$("#q-timer-widget").fadeIn();
|
$("#q-timer-widget").fadeIn()
|
||||||
time_remaining = get_time_remaining();
|
time_remaining = get_time_remaining()
|
||||||
clock = setInterval(timer, 1000);
|
clock = setInterval(timer, 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function check_started() {
|
function check_started() {
|
||||||
let questions = window.localStorage.getItem('questions');
|
let questions = window.localStorage.getItem('questions')
|
||||||
let time_limit = window.localStorage.getItem('time_limit');
|
let time_limit = window.localStorage.getItem('time_limit')
|
||||||
let start_time = window.localStorage.getItem('start_time')
|
let start_time = window.localStorage.getItem('start_time')
|
||||||
if (questions != null && start_time != null && time_limit != null) {
|
if (questions != null && start_time != null && time_limit != null) {
|
||||||
start();
|
start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_time_remaining() {
|
function get_time_remaining() {
|
||||||
var end_time = new Date(time_limit).getTime();
|
var end_time = new Date(time_limit).getTime()
|
||||||
var _start_time = new Date().getTime();
|
var _start_time = new Date().getTime()
|
||||||
return end_time - _start_time;
|
return end_time - _start_time
|
||||||
}
|
}
|
||||||
|
|
||||||
function timer() {
|
function timer() {
|
||||||
var hours = Math.floor((time_remaining % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
var hours = Math.floor((time_remaining % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
|
||||||
var minutes = Math.floor((time_remaining % (1000 * 60 * 60)) / (1000 * 60));
|
var minutes = Math.floor((time_remaining % (1000 * 60 * 60)) / (1000 * 60))
|
||||||
var seconds = Math.floor((time_remaining % (1000 * 60)) / 1000);
|
var seconds = Math.floor((time_remaining % (1000 * 60)) / 1000)
|
||||||
|
|
||||||
if (time_remaining > 0) {
|
if (time_remaining > 0) {
|
||||||
var timer_display = '';
|
var timer_display = ''
|
||||||
if (hours > 0) {
|
if (hours > 0) {
|
||||||
timer_display = `${hours.toString()}:`;
|
timer_display = `${hours.toString()}:`
|
||||||
}
|
}
|
||||||
if (minutes > 0 || hours > 0) {
|
if (minutes > 0 || hours > 0) {
|
||||||
if (minutes < 10) {
|
if (minutes < 10) {
|
||||||
timer_display += `0${minutes.toString()}:`;
|
timer_display += `0${minutes.toString()}:`
|
||||||
} else {
|
} else {
|
||||||
timer_display += `${minutes.toString()}:`;
|
timer_display += `${minutes.toString()}:`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (seconds < 10) {
|
if (seconds < 10) {
|
||||||
timer_display += `0${seconds.toString()}`;
|
timer_display += `0${seconds.toString()}`
|
||||||
} else {
|
} else {
|
||||||
timer_display += seconds.toString();
|
timer_display += seconds.toString()
|
||||||
}
|
}
|
||||||
$timer.html(timer_display);
|
$timer.html(timer_display)
|
||||||
time_remaining -= 1000
|
time_remaining -= 1000
|
||||||
} else {
|
} else {
|
||||||
$timer.html('Expired');
|
$timer.html('Expired')
|
||||||
clearInterval(clock);
|
clearInterval(clock)
|
||||||
stop()
|
stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop() {
|
function stop() {
|
||||||
$quiz_render.fadeOut();
|
$quiz_render.fadeOut()
|
||||||
$quiz_navigator.fadeOut();
|
$quiz_navigator.fadeOut()
|
||||||
$quiz_timeout.fadeIn();
|
$quiz_timeout.fadeIn()
|
||||||
$("#btn-toggle-navigator").addClass('disabled');
|
$("#btn-toggle-navigator").addClass('disabled')
|
||||||
$("#btn-toggle-settings").addClass('disabled')
|
$("#btn-toggle-settings").addClass('disabled')
|
||||||
}
|
}
|
||||||
|
|
||||||
function count_questions(status) {
|
function count_questions(status) {
|
||||||
output = 0;
|
output = 0
|
||||||
for (let i = 0; i < Object.keys(question_status).length; i++) {
|
for (let i = 0; i < Object.keys(question_status).length; i++) {
|
||||||
key = Object.keys(question_status)[i];
|
key = Object.keys(question_status)[i]
|
||||||
if (question_status[key] == status){
|
if (question_status[key] == status){
|
||||||
output ++;
|
output ++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return output;
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
// Variable Definitions
|
// Variable Definitions
|
||||||
|
|
||||||
const id = window.localStorage.getItem('id');
|
const id = window.localStorage.getItem('id')
|
||||||
|
|
||||||
var current_question = 0;
|
var current_question = 0
|
||||||
var total_questions = 0;
|
var total_questions = 0
|
||||||
var question_status = {};
|
var question_status = {}
|
||||||
var answers = {};
|
var answers = {}
|
||||||
var questions = [];
|
var questions = []
|
||||||
var time_limit, start_time, time_remaining;
|
var time_limit, start_time, time_remaining
|
||||||
|
|
||||||
var display_settings = get_settings_from_storage();
|
var display_settings = get_settings_from_storage()
|
||||||
|
|
||||||
const $quiz_settings = $("#quiz-settings");
|
const $quiz_settings = $("#quiz-settings")
|
||||||
const $quiz_navigator = $("#quiz-navigator");
|
const $quiz_navigator = $("#quiz-navigator")
|
||||||
const $quiz_render = $("#quiz-render");
|
const $quiz_render = $("#quiz-render")
|
||||||
const $quiz_timeout = $("#quiz-timeout");
|
const $quiz_timeout = $("#quiz-timeout")
|
||||||
const $nav_flag = $("#q-nav-flag");
|
const $nav_flag = $("#q-nav-flag")
|
||||||
const $nav_next = $("#q-nav-next");
|
const $nav_next = $("#q-nav-next")
|
||||||
const $nav_prev = $("#q-nav-prev");
|
const $nav_prev = $("#q-nav-prev")
|
||||||
const $nav_container = $("#navigator-container");
|
const $nav_container = $("#navigator-container")
|
||||||
const $timer = $("#q-timer-display");
|
const $timer = $("#q-timer-display")
|
||||||
var clock
|
var clock
|
||||||
|
|
||||||
var toggle_settings = false;
|
var toggle_settings = false
|
||||||
var toggle_navigator = false;
|
var toggle_navigator = false
|
||||||
|
|
||||||
const $question_title = $("#quiz-question-title");
|
const $question_title = $("#quiz-question-title")
|
||||||
const $question_header = $("#quiz-question-header");
|
const $question_header = $("#quiz-question-header")
|
||||||
const $question_text = $("#quiz-question-text");
|
const $question_text = $("#quiz-question-text")
|
||||||
const $question_options = $("#quiz-question-options");
|
const $question_options = $("#quiz-question-options")
|
||||||
|
|
||||||
const $progress_skipped = $("#skipped-bar");
|
const $progress_skipped = $("#skipped-bar")
|
||||||
const $progress_answered = $("#answered-bar");
|
const $progress_answered = $("#answered-bar")
|
||||||
const $progress_flagged = $("#flagged-bar");
|
const $progress_flagged = $("#flagged-bar")
|
||||||
const $skipped_count = $("#skipped-count");
|
const $skipped_count = $("#skipped-count")
|
||||||
const $answered_count = $("#answered-count");
|
const $answered_count = $("#answered-count")
|
||||||
const $flagged_count = $("#flagged-count");
|
const $flagged_count = $("#flagged-count")
|
||||||
|
|
||||||
// Execution on Load
|
// Execution on Load
|
||||||
|
|
||||||
apply_settings(display_settings);
|
apply_settings(display_settings)
|
||||||
check_started();
|
check_started()
|
@ -1,21 +1,21 @@
|
|||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$("#od-font-test").click(function(){
|
$("#od-font-test").click(function(){
|
||||||
$("body").css("font-family", "opendyslexic3regular")
|
$("body").css("font-family", "opendyslexic3regular")
|
||||||
});
|
})
|
||||||
|
|
||||||
$('.test-code-input').keyup(function() {
|
$('.test-code-input').keyup(function() {
|
||||||
var input = $(this).val().split("-").join("").split("—").join("");
|
var input = $(this).val().split("-").join("").split("—").join("")
|
||||||
if (input.length > 0) {
|
if (input.length > 0) {
|
||||||
input = input.match(new RegExp('.{1,4}', 'g')).join("—");
|
input = input.match(new RegExp('.{1,4}', 'g')).join("—")
|
||||||
}
|
}
|
||||||
$(this).val(input);
|
$(this).val(input)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
$('form[name=form-quiz-start]').submit(function(event) {
|
$('form[name=form-quiz-start]').submit(function(event) {
|
||||||
|
|
||||||
var $form = $(this);
|
var $form = $(this)
|
||||||
var data = $form.serialize();
|
var data = $form.serialize()
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: window.location.pathname,
|
url: window.location.pathname,
|
||||||
@ -24,21 +24,21 @@ $('form[name=form-quiz-start]').submit(function(event) {
|
|||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
var id = response.id
|
var id = response.id
|
||||||
window.localStorage.setItem('id', id);
|
window.localStorage.setItem('id', id)
|
||||||
window.location.href = `/quiz/`;
|
window.location.href = `/quiz/`
|
||||||
},
|
},
|
||||||
error: function(response) {
|
error: function(response) {
|
||||||
error_response(response);
|
error_response(response)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
function error_response(response) {
|
function error_response(response) {
|
||||||
|
|
||||||
const $alert = $("#alert-box");
|
const $alert = $("#alert-box")
|
||||||
$alert.html('');
|
$alert.html('')
|
||||||
|
|
||||||
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
||||||
$alert.html(`
|
$alert.html(`
|
||||||
@ -47,18 +47,18 @@ function error_response(response) {
|
|||||||
${response.responseJSON.error}
|
${response.responseJSON.error}
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`)
|
||||||
} else if (response.responseJSON.error instanceof Array) {
|
} else if (response.responseJSON.error instanceof Array) {
|
||||||
var output = ''
|
var output = ''
|
||||||
for (var i = 0; i < response.responseJSON.error.length; i ++) {
|
for (let i = 0; i < response.responseJSON.error.length; i ++) {
|
||||||
output += `
|
output += `
|
||||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
||||||
${response.responseJSON.error[i]}
|
${response.responseJSON.error[i]}
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`
|
||||||
$alert.html(output);
|
$alert.html(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,13 +74,13 @@ $('#dismiss-cookie-alert').click(function(event){
|
|||||||
},
|
},
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(response){
|
success: function(response){
|
||||||
console.log(response);
|
console.log(response)
|
||||||
},
|
},
|
||||||
error: function(response){
|
error: function(response){
|
||||||
console.log(response);
|
console.log(response)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
|
|
||||||
})
|
})
|
@ -1,21 +1,21 @@
|
|||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$("#od-font-test").click(function(){
|
$("#od-font-test").click(function(){
|
||||||
$("body").css("font-family", "opendyslexic3regular")
|
$("body").css("font-family", "opendyslexic3regular")
|
||||||
});
|
})
|
||||||
|
|
||||||
$('.test-code-input').keyup(function() {
|
$('.test-code-input').keyup(function() {
|
||||||
var input = $(this).val().split("-").join("").split("—").join("");
|
var input = $(this).val().split("-").join("").split("—").join("")
|
||||||
if (input.length > 0) {
|
if (input.length > 0) {
|
||||||
input = input.match(new RegExp('.{1,4}', 'g')).join("—");
|
input = input.match(new RegExp('.{1,4}', 'g')).join("—")
|
||||||
}
|
}
|
||||||
$(this).val(input);
|
$(this).val(input)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
$('form[name=form-quiz-start]').submit(function(event) {
|
$('form[name=form-quiz-start]').submit(function(event) {
|
||||||
|
|
||||||
var $form = $(this);
|
var $form = $(this)
|
||||||
var data = $form.serialize();
|
var data = $form.serialize()
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: window.location.pathname,
|
url: window.location.pathname,
|
||||||
@ -24,21 +24,21 @@ $('form[name=form-quiz-start]').submit(function(event) {
|
|||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
var id = response.id
|
var id = response.id
|
||||||
window.localStorage.setItem('id', id);
|
window.localStorage.setItem('id', id)
|
||||||
window.location.href = `/quiz/`;
|
window.location.href = `/quiz/`
|
||||||
},
|
},
|
||||||
error: function(response) {
|
error: function(response) {
|
||||||
error_response(response);
|
error_response(response)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
});
|
})
|
||||||
|
|
||||||
function error_response(response) {
|
function error_response(response) {
|
||||||
|
|
||||||
const $alert = $("#alert-box");
|
const $alert = $("#alert-box")
|
||||||
$alert.html('');
|
$alert.html('')
|
||||||
|
|
||||||
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
||||||
$alert.html(`
|
$alert.html(`
|
||||||
@ -47,18 +47,18 @@ function error_response(response) {
|
|||||||
${response.responseJSON.error}
|
${response.responseJSON.error}
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`)
|
||||||
} else if (response.responseJSON.error instanceof Array) {
|
} else if (response.responseJSON.error instanceof Array) {
|
||||||
var output = ''
|
var output = ''
|
||||||
for (var i = 0; i < response.responseJSON.error.length; i ++) {
|
for (let i = 0; i < response.responseJSON.error.length; i ++) {
|
||||||
output += `
|
output += `
|
||||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
||||||
${response.responseJSON.error[i]}
|
${response.responseJSON.error[i]}
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`
|
||||||
$alert.html(output);
|
$alert.html(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,13 +74,13 @@ $('#dismiss-cookie-alert').click(function(event){
|
|||||||
},
|
},
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(response){
|
success: function(response){
|
||||||
console.log(response);
|
console.log(response)
|
||||||
},
|
},
|
||||||
error: function(response){
|
error: function(response){
|
||||||
console.log(response);
|
console.log(response)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
|
|
||||||
})
|
})
|
@ -1,8 +1,13 @@
|
|||||||
|
from ..models import Dataset
|
||||||
|
|
||||||
from flask import current_app as app
|
from flask import current_app as app
|
||||||
|
from flask import flash, redirect
|
||||||
|
from flask.helpers import url_for
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from random import shuffle
|
from random import shuffle
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
def load(filename:str):
|
def load(filename:str):
|
||||||
data_dir = Path(app.config.get('DATA'))
|
data_dir = Path(app.config.get('DATA'))
|
||||||
@ -66,4 +71,14 @@ def get_tag_list(dataset:list):
|
|||||||
if block['type'] == 'question': output = list(set(output) | set(block['tags']))
|
if block['type'] == 'question': output = list(set(output) | set(block['tags']))
|
||||||
if block['type'] == 'block':
|
if block['type'] == 'block':
|
||||||
for question in block['questions']: output = list(set(output) | set(question['tags']))
|
for question in block['questions']: output = list(set(output) | set(question['tags']))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
def check_dataset_exists(function):
|
||||||
|
@wraps(function)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
datasets = Dataset.query.all()
|
||||||
|
if not datasets:
|
||||||
|
flash('There are no available question datasets. Please upload a question dataset first, or use the question editor to create a new dataset.', 'error')
|
||||||
|
return redirect(url_for('admin._questions'))
|
||||||
|
return function(*args, **kwargs)
|
||||||
|
return wrapper
|
0
ref-test/app/view/__init__.py
Normal file
0
ref-test/app/view/__init__.py
Normal file
260
ref-test/app/view/static/css/style.css
Normal file
260
ref-test/app/view/static/css/style.css
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
body {
|
||||||
|
padding: 80px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer {
|
||||||
|
background-color: lightgray;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 40px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-display {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 420px;
|
||||||
|
padding: 15px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-heading {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input,
|
||||||
|
.form-label-group label {
|
||||||
|
padding: var(--input-padding-y) var(--input-padding-x);
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 0; /* Override default `<label>` margin */
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #495057;
|
||||||
|
cursor: text; /* Match the input under the label */
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: .25rem;
|
||||||
|
transition: all .1s ease-in-out;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0%;
|
||||||
|
border-bottom: 2px solid #585858;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input:active, .form-label-group input:focus {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input::-webkit-input-placeholder {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input:-ms-input-placeholder {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input::-ms-input-placeholder {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input::-moz-placeholder {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input::placeholder {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input:not(:placeholder-shown) {
|
||||||
|
padding-top: calc(var(--input-padding-y) + var(--input-padding-y) * (2 / 3));
|
||||||
|
padding-bottom: calc(var(--input-padding-y) / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label-group input:not(:placeholder-shown) ~ label {
|
||||||
|
padding-top: calc(var(--input-padding-y) / 3);
|
||||||
|
padding-bottom: calc(var(--input-padding-y) / 3);
|
||||||
|
font-size: 12px;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-check {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox input {
|
||||||
|
transform: scale(1.5);
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signin-forgot-password {
|
||||||
|
font-size: 14pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-submission-button {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-submission-button button, .form-submission-button a {
|
||||||
|
margin: 1rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-submission-button button span, .form-submission-button button svg, .form-submission-button a span, .form-submission-button a svg {
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.dataTable {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-actions {
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataTables_wrapper .dt-buttons {
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
float:none;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-actions button, .row-actions a {
|
||||||
|
margin: 0px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cookie-alert {
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dismiss-cookie-alert {
|
||||||
|
margin-top: 16px;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-db-empty {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 720px;
|
||||||
|
font-size: 14pt;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-date-input, .form-select-input {
|
||||||
|
position: relative;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-date-input input,
|
||||||
|
.form-date-input label, .form-select-input select, .form-select-input label {
|
||||||
|
padding: var(--input-padding-y) var(--input-padding-x);
|
||||||
|
font-size: 16pt;
|
||||||
|
width: 100%;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid #585858;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker::-webkit-calendar-picker-indicator {
|
||||||
|
border: 1px;
|
||||||
|
border-color: gray;
|
||||||
|
border-radius: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-date-input label, .form-select-input label {
|
||||||
|
/* position: absolute; */
|
||||||
|
/* top: 0;
|
||||||
|
left: 0; */
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 0; /* Override default `<label>` margin */
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #495057;
|
||||||
|
cursor: text; /* Match the input under the label */
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: .25rem;
|
||||||
|
transition: all .1s ease-in-out;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-upload {
|
||||||
|
margin: 2rem 0;
|
||||||
|
font-size: 14pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-action-buttons, .test-action {
|
||||||
|
margin: 5px auto;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-item {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Change Autocomplete styles in Chrome*/
|
||||||
|
input:-webkit-autofill,
|
||||||
|
input:-webkit-autofill:hover,
|
||||||
|
input:-webkit-autofill:focus,
|
||||||
|
textarea:-webkit-autofill,
|
||||||
|
textarea:-webkit-autofill:hover,
|
||||||
|
textarea:-webkit-autofill:focus,
|
||||||
|
select:-webkit-autofill,
|
||||||
|
select:-webkit-autofill:hover,
|
||||||
|
select:-webkit-autofill:focus {
|
||||||
|
transition: background-color 5000s ease-in-out 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fallback for Edge
|
||||||
|
-------------------------------------------------- */
|
||||||
|
@supports (-ms-ime-align: auto) {
|
||||||
|
.form-label-group label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.form-label-group input::-ms-input-placeholder {
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fallback for IE
|
||||||
|
-------------------------------------------------- */
|
||||||
|
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
||||||
|
.form-label-group label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.form-label-group input:-ms-input-placeholder {
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
30
ref-test/app/view/static/css/view.css
Normal file
30
ref-test/app/view/static/css/view.css
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
.info-panel {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-panel {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 0;
|
||||||
|
width:fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
#alert-box {
|
||||||
|
margin: 30px auto;
|
||||||
|
max-width: 460px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
border: 2px solid black;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 10px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-body, .question-block {
|
||||||
|
padding: 0px 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
padding: 0px 2em;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
2
ref-test/app/view/static/js/jquery-3.6.0.min.js
vendored
Normal file
2
ref-test/app/view/static/js/jquery-3.6.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
115
ref-test/app/view/static/js/script.js
Normal file
115
ref-test/app/view/static/js/script.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// Menu Highlight Scripts
|
||||||
|
const menuItems = document.getElementsByClassName('nav-link')
|
||||||
|
for(let i = 0; i < menuItems.length; i++) {
|
||||||
|
if(menuItems[i].pathname == window.location.pathname) {
|
||||||
|
menuItems[i].classList.add('active')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const dropdownItems = document.getElementsByClassName('dropdown-item')
|
||||||
|
for(let i = 0; i< dropdownItems.length; i++) {
|
||||||
|
if(dropdownItems[i].pathname == window.location.pathname) {
|
||||||
|
dropdownItems[i].classList.add('active')
|
||||||
|
$( "#" + dropdownItems[i].id ).closest( '.dropdown' ).find('.dropdown-toggle').addClass('active')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// General Post Method Form Processing Script
|
||||||
|
$('form.form-post').submit(function(event) {
|
||||||
|
|
||||||
|
var $form = $(this)
|
||||||
|
var data = $form.serialize()
|
||||||
|
var url = $(this).prop('action')
|
||||||
|
var rel_success = $(this).data('rel-success')
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
type: 'POST',
|
||||||
|
data: data,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
if (response.redirect_to) {
|
||||||
|
window.location.href = response.redirect_to
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
window.location.href = rel_success
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(response) {
|
||||||
|
error_response(response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
})
|
||||||
|
|
||||||
|
function error_response(response) {
|
||||||
|
|
||||||
|
const $alert = $("#alert-box")
|
||||||
|
$alert.html('')
|
||||||
|
|
||||||
|
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
||||||
|
$alert.html(`
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
||||||
|
${response.responseJSON.error}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
} else if (response.responseJSON.error instanceof Array) {
|
||||||
|
var output = ''
|
||||||
|
for (let i = 0; i < response.responseJSON.error.length; i ++) {
|
||||||
|
output += `
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
|
||||||
|
${response.responseJSON.error[i]}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
$alert.html(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$alert.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dismiss Cookie Alert
|
||||||
|
$('#dismiss-cookie-alert').click(function(event){
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/cookies/',
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
time: Date.now()
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response){
|
||||||
|
console.log(response)
|
||||||
|
},
|
||||||
|
error: function(response){
|
||||||
|
console.log(response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create New Dataset
|
||||||
|
$('.create-new-dataset').click(function(event){
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/editor/new/',
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
time: Date.now()
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response){
|
||||||
|
if (response.redirect_to) {
|
||||||
|
window.location.href = response.redirect_to
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(response){
|
||||||
|
console.log(response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
event.preventDefault()
|
||||||
|
})
|
130
ref-test/app/view/static/js/view.js
Normal file
130
ref-test/app/view/static/js/view.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
// Variable Declarations
|
||||||
|
const $control_panel = $('.control-panel')
|
||||||
|
const $info_panel = $('.info-panel')
|
||||||
|
const $viewer_panel = $('.viewer-panel')
|
||||||
|
|
||||||
|
var element_index = 0
|
||||||
|
|
||||||
|
// Info Button Listener
|
||||||
|
$control_panel.find('button').click(function(event){
|
||||||
|
if ($info_panel.is(":hidden")) {
|
||||||
|
$viewer_panel.hide()
|
||||||
|
$info_panel.fadeIn()
|
||||||
|
$(this).addClass('active')
|
||||||
|
} else {
|
||||||
|
$info_panel.hide()
|
||||||
|
$viewer_panel.fadeIn()
|
||||||
|
$(this).removeClass('active')
|
||||||
|
}
|
||||||
|
event.preventDefault()
|
||||||
|
})
|
||||||
|
|
||||||
|
function parse_data(data) {
|
||||||
|
var block
|
||||||
|
var obj
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
block = data[i]
|
||||||
|
obj = document.createElement('div')
|
||||||
|
obj.classList = 'block'
|
||||||
|
if (block['type'] == 'question') {
|
||||||
|
text = document.createElement('p')
|
||||||
|
text.innerHTML = `<strong>Question ${block['q_no'] + 1}.</strong> ${block['text']}`
|
||||||
|
obj.append(text)
|
||||||
|
question_body = document.createElement('div')
|
||||||
|
question_body.className ='question-body'
|
||||||
|
type = document.createElement('p')
|
||||||
|
type.innerHTML = `<strong>Question Type:</strong> ${block['q_type']}`
|
||||||
|
question_body.append(type)
|
||||||
|
options = document.createElement('p')
|
||||||
|
options.innerHTML = '<strong>Options:</strong>'
|
||||||
|
option_list = document.createElement('ul')
|
||||||
|
for (let _i = 0; _i < block['options'].length; _i++) {
|
||||||
|
option = document.createElement('li')
|
||||||
|
option.innerHTML = block['options'][_i]
|
||||||
|
if (block['correct'] == _i) {
|
||||||
|
option.innerHTML += ' <span class="badge rounded-pill bg-success">Correct</span>'
|
||||||
|
}
|
||||||
|
option_list.append(option)
|
||||||
|
}
|
||||||
|
options.append(option_list)
|
||||||
|
question_body.append(options)
|
||||||
|
tags = document.createElement('p')
|
||||||
|
tags.innerHTML = `<strong>Tags:</strong>`
|
||||||
|
tag_list = document.createElement('ul')
|
||||||
|
for (let _i = 0; _i < block['tags'].length; _i++) {
|
||||||
|
tag = document.createElement('li')
|
||||||
|
tag.innerHTML = block['tags'][_i]
|
||||||
|
tag_list.append(tag)
|
||||||
|
}
|
||||||
|
tags.append(tag_list)
|
||||||
|
question_body.append(tags)
|
||||||
|
obj.append(question_body)
|
||||||
|
} else if (block['type'] == 'block') {
|
||||||
|
meta = document.createElement('p')
|
||||||
|
meta.innerHTML = `<strong>Block ${i+1}.</strong> ${block['questions'].length} questions.`
|
||||||
|
obj.append(meta)
|
||||||
|
header = document.createElement('blockquote')
|
||||||
|
header.innerText = block['question_header']
|
||||||
|
obj.append(header)
|
||||||
|
var block_question = document.createElement('div')
|
||||||
|
var question
|
||||||
|
block_question.className = 'question-block'
|
||||||
|
for (let _i = 0; _i < block['questions'].length; _i++) {
|
||||||
|
question = block['questions'][_i]
|
||||||
|
text = document.createElement('p')
|
||||||
|
text.innerHTML = `<strong>Question ${question['q_no'] + 1}.</strong> ${question['text']}`
|
||||||
|
block_question.append(text)
|
||||||
|
question_body = document.createElement('div')
|
||||||
|
question_body.className ='question-body'
|
||||||
|
type = document.createElement('p')
|
||||||
|
type.innerHTML = `<strong>Question Type:</strong> ${question['q_type']}`
|
||||||
|
question_body.append(type)
|
||||||
|
options = document.createElement('p')
|
||||||
|
options.innerHTML = '<strong>Options:</strong>'
|
||||||
|
option_list = document.createElement('ul')
|
||||||
|
for (let __i = 0; __i < question['options'].length; __i++) {
|
||||||
|
option = document.createElement('li')
|
||||||
|
option.innerHTML = question['options'][__i]
|
||||||
|
if (question['correct'] == __i) {
|
||||||
|
option.innerHTML += ' <span class="badge rounded-pill bg-success">Correct</span>'
|
||||||
|
}
|
||||||
|
option_list.append(option)
|
||||||
|
}
|
||||||
|
options.append(option_list)
|
||||||
|
question_body.append(options)
|
||||||
|
tags = document.createElement('p')
|
||||||
|
tags.innerHTML = `<strong>Tags:</strong>`
|
||||||
|
tag_list = document.createElement('ul')
|
||||||
|
for (let __i = 0; __i < question['tags'].length; __i++) {
|
||||||
|
tag = document.createElement('li')
|
||||||
|
tag.innerHTML = question['tags'][__i]
|
||||||
|
tag_list.append(tag)
|
||||||
|
}
|
||||||
|
tags.append(tag_list)
|
||||||
|
question_body.append(tags)
|
||||||
|
block_question.append(question_body)
|
||||||
|
obj.append(block_question)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$viewer_panel.append(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch data once page finishes loading
|
||||||
|
$(window).on('load', function() {
|
||||||
|
$.ajax({
|
||||||
|
url: target,
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify({
|
||||||
|
'id': id,
|
||||||
|
'action': 'fetch'
|
||||||
|
}),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(response) {
|
||||||
|
parse_data(response['data'])
|
||||||
|
},
|
||||||
|
error: function(response) {
|
||||||
|
console.log(response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
84
ref-test/app/view/templates/view/components/base.html
Normal file
84
ref-test/app/view/templates/view/components/base.html
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css">
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="{{ url_for('.static', filename='css/style.css') }}"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="{{ url_for('.static', filename='css/view.css') }}"
|
||||||
|
/>
|
||||||
|
{% block style %}
|
||||||
|
{% endblock %}
|
||||||
|
<title>{% block title %} SKA Referee Test | Admin Console {% endblock %}</title>
|
||||||
|
{% include "view/components/og-meta.html" %}
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
|
||||||
|
{% block navbar %}
|
||||||
|
{% include "view/components/navbar.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{% block top_alerts %}
|
||||||
|
{% include "view/components/server-alerts.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="container site-footer mt-5">
|
||||||
|
{% block footer %}
|
||||||
|
{% include "view/components/footer.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- JQuery, Popper, and Bootstrap js dependencies -->
|
||||||
|
<script
|
||||||
|
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||||
|
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
|
||||||
|
crossorigin="anonymous">
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
window.jQuery || document.write(`<script src="{{ url_for('.static', filename='js/jquery-3.6.0.min.js') }}"><\/script>`)
|
||||||
|
</script>
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"
|
||||||
|
integrity="sha384-7+zCNj/IqJ95wo16oMtfsKbZ9ccEh31eOz1HGyDuCQ6wgnyJNSYdrPa03rtR1zdB"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
</script>
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js"
|
||||||
|
integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
></script>
|
||||||
|
<!-- Custom js -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
var csrf_token = "{{ csrf_token() }}";
|
||||||
|
|
||||||
|
$.ajaxSetup({
|
||||||
|
beforeSend: function(xhr, settings) {
|
||||||
|
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
|
||||||
|
xhr.setRequestHeader("X-CSRFToken", csrf_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="{{ url_for('.static', filename='js/script.js') }}"
|
||||||
|
></script>
|
||||||
|
{% block script %}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1 @@
|
|||||||
|
<div id="alert-box" tabindex="-1"></div>
|
28
ref-test/app/view/templates/view/components/datatable.html
Normal file
28
ref-test/app/view/templates/view/components/datatable.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{% extends "view/components/base.html" %}
|
||||||
|
{% block datatable_css %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.11.3/css/dataTables.bootstrap5.min.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/buttons/2.0.1/css/buttons.bootstrap5.min.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/colreorder/1.5.5/css/colReorder.bootstrap5.min.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/fixedheader/3.2.0/css/fixedHeader.bootstrap5.min.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/keytable/2.6.4/css/keyTable.bootstrap5.min.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/responsive/2.2.9/css/responsive.bootstrap5.min.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/searchbuilder/1.3.0/css/searchBuilder.dataTables.min.css"/>
|
||||||
|
{% endblock %}
|
||||||
|
{% block datatable_scripts %}
|
||||||
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/2.5.0/jszip.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/pdfmake.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/vfs_fonts.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.datatables.net/1.11.3/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.datatables.net/1.11.3/js/dataTables.bootstrap5.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/dataTables.buttons.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.bootstrap5.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.colVis.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.html5.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.print.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.datatables.net/colreorder/1.5.5/js/dataTables.colReorder.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.datatables.net/fixedheader/3.2.0/js/dataTables.fixedHeader.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.datatables.net/keytable/2.6.4/js/dataTables.keyTable.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.9/js/dataTables.responsive.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.9/js/responsive.bootstrap5.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.datatables.net/searchbuilder/1.3.0/js/dataTables.searchBuilder.min.js"></script>
|
||||||
|
{% endblock %}
|
2
ref-test/app/view/templates/view/components/footer.html
Normal file
2
ref-test/app/view/templates/view/components/footer.html
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<p>This web app was developed by Vivek Santayana. The source code for the web app, excluding any data pertaining to the questions in the quiz, is freely available at <a href="https://git.vsnt.uk/viveksantayana/ska-referee-test">Vivek’s personal GIT repository</a> under an MIT License.</p>
|
||||||
|
<p>All questions in the test are © The Scottish Korfball Association {{ now.year }}. All rights are reserved.</p>
|
@ -0,0 +1,4 @@
|
|||||||
|
{% extends "view/components/base.html" %}
|
||||||
|
{% import "bootstrap/wtf.html" as wtf %}
|
||||||
|
{% block top_alerts %}
|
||||||
|
{% endblock %}
|
117
ref-test/app/view/templates/view/components/navbar.html
Normal file
117
ref-test/app/view/templates/view/components/navbar.html
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
|
||||||
|
<div class="container">
|
||||||
|
<a href="{{ url_for('admin._home') }}" class="navbar-brand mb-0 h1">RefTest (Beta) | Admin</a>
|
||||||
|
<button
|
||||||
|
class="navbar-toggler"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbar"
|
||||||
|
aria-controls="navbar"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="Toggle Navigation"
|
||||||
|
>
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse justify-content-end" id="navbar">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
{% if not current_user.is_authenticated %}
|
||||||
|
<li class="nav-item" id="nav-login">
|
||||||
|
<a href="{{ url_for('admin._login') }}" id="link-login" class="nav-link">Log In</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<li class="nav-item" id="nav-results">
|
||||||
|
<a href="{{ url_for('admin._view_entries') }}" id="link-results" class="nav-link">View Results</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item dropdown" id="nav-tests">
|
||||||
|
<a
|
||||||
|
class="nav-link dropdown-toggle"
|
||||||
|
id="dropdown-tests"
|
||||||
|
role="button"
|
||||||
|
href="{{ url_for('admin._tests') }}"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
Exams
|
||||||
|
</a>
|
||||||
|
<ul
|
||||||
|
class="dropdown-menu"
|
||||||
|
aria-labelledby="dropdown-settings"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('admin._tests', filter='active') }}" id="link-active" class="dropdown-item">Active</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('admin._tests', filter='scheduled') }}" id="link-scheduled" class="dropdown-item">Scheduled</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('admin._tests', filter='expired') }}" id="link-expired" class="dropdown-item">Expired</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('admin._tests', filter='all') }}" id="link-all" class="dropdown-item">All</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('admin._tests', filter='create') }}" id="link-create" class="dropdown-item">Create</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item dropdown" id="nav-settings">
|
||||||
|
<a
|
||||||
|
class="nav-link dropdown-toggle"
|
||||||
|
id="dropdown-account"
|
||||||
|
role="button"
|
||||||
|
href="{{ url_for('admin._settings') }}"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</a>
|
||||||
|
<ul
|
||||||
|
class="dropdown-menu"
|
||||||
|
aria-labelledby="dropdown-settings"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('admin._settings') }}" id="link-settings" class="dropdown-item">View Settings</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('admin._users') }}" id="link-users" class="dropdown-item">Users</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('admin._questions') }}" id="link-questions" class="dropdown-item">Manage Questions</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('view._view') }}" id="link-editor" class="dropdown-item">View Questions</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('editor._editor') }}" id="link-editor" class="dropdown-item">Edit Questions</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item dropdown" id="nav-account">
|
||||||
|
<a
|
||||||
|
class="nav-link dropdown-toggle"
|
||||||
|
id="dropdown-account"
|
||||||
|
role="button"
|
||||||
|
href="{{ url_for('admin._update_user', id=current_user.id) }}"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
Account
|
||||||
|
</a>
|
||||||
|
<ul
|
||||||
|
class="dropdown-menu"
|
||||||
|
aria-labelledby="dropdown-account"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('admin._update_user', id=current_user.id) }}" id="link-account" class="dropdown-item">Account Settings</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('admin._logout') }}" id="link-logout" class="dropdown-item">Log Out</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
18
ref-test/app/view/templates/view/components/og-meta.html
Normal file
18
ref-test/app/view/templates/view/components/og-meta.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<meta name="description" content="A web app for taking the Scottish Korfball Association Refereeing Theory Exam on-line." />
|
||||||
|
<meta property="og:locale" content="en_UK" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:description" content="A web app for taking the Scottish Korfball Association Refereeing Theory Exam on-line." />
|
||||||
|
<meta property="og:url" content="{{ url_for(request.endpoint, _external = True, **(request.view_args or {})) }}" />
|
||||||
|
<meta property="og:site_name" content="Scottish Korfball Association Referee Theory Exam" />
|
||||||
|
<meta property="og:image" content="{{ url_for('.static', filename='favicon.png', _external = True) }}" />
|
||||||
|
<meta property="og:image:alt" content="Logo of the SKA Refereeing Exam App" />
|
||||||
|
<meta property="og:image:width" content="512" />
|
||||||
|
<meta property="og:image:height" content="512" />
|
||||||
|
<meta name="twitter:card" content="summary" />
|
||||||
|
<meta name="twitter:description" content="A web app for taking the Scottish Korfball Association Refereeing Theory Exam on-line." />
|
||||||
|
<meta name="twitter:image" content="{{ url_for('.static', filename='favicon.png', _external = True) }}" />
|
||||||
|
<meta name="twitter:image:alt" content="Logo of the SKA Refereeing Exam App" />
|
||||||
|
<meta name="twitter:creator" content="@viveksantayana" />
|
||||||
|
<meta name="twitter:site" content="@viveksantayana" />
|
||||||
|
<meta name="theme-color" content="#343a40" />
|
||||||
|
<link rel="shortcut icon" href="{{ url_for('.static', filename='favicon.ico') }}">
|
@ -0,0 +1,23 @@
|
|||||||
|
<div class="navbar navbar-expand-sm navbar-light bg-light">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="expand navbar-expand justify-content-center" id="navbar_secondary">
|
||||||
|
<ul class="nav nav-pills">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('admin._tests', filter='active') }}">Active</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('admin._tests', filter='scheduled') }}">Scheduled</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('admin._tests', filter='expired') }}">Expired</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('admin._tests', filter='all') }}">All</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('admin._tests', filter='create') }}">Create</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,43 @@
|
|||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% set cookie_flash_flag = namespace(value=False) %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
{% if category == "error" %}
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill" title="Error" aria-title="Error"></i>
|
||||||
|
{{ message|safe }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% elif category == "success" %}
|
||||||
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-check2-circle" title="Success" aria-title="Success"></i>
|
||||||
|
{{ message|safe }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% elif category == "warning" %}
|
||||||
|
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-info-circle-fill" aria-title="Warning" title="Warning"></i>
|
||||||
|
{{ message|safe }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% elif category == "cookie_alert" %}
|
||||||
|
{% if not cookie_flash_flag.value %}
|
||||||
|
<div class="alert alert-primary alert-dismissible fade show" id="cookie-alert" role="alert">
|
||||||
|
<i class="bi bi-info-circle-fill" title="Cookie Alert" aria-title="Cookie Alert"></i>
|
||||||
|
{{ message|safe }}
|
||||||
|
<div class="d-flex justify-content-center w-100">
|
||||||
|
<button type="button" id="dismiss-cookie-alert" class="btn btn-success" data-bs-dismiss="alert" aria-label="Close">Accept</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% set cookie_flash_flag.value = True %}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-primary alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-info-circle-fill" title="Alert"></i>
|
||||||
|
{{ message|safe }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
116
ref-test/app/view/templates/view/console.html
Normal file
116
ref-test/app/view/templates/view/console.html
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
{% extends "view/components/base.html" %}
|
||||||
|
|
||||||
|
{% block style %}
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="{{ url_for('.static', filename='css/view.css') }}"
|
||||||
|
/>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>View Questions</h1>
|
||||||
|
<div class="container">
|
||||||
|
<p class="lead">
|
||||||
|
This page lists all the questions in the selected dataset.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="container control-panel">
|
||||||
|
<button class="btn btn-primary" aria-title="Information" title="Information"><i class="bi bi-info-circle-fill"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="container info-panel">
|
||||||
|
<h3>
|
||||||
|
Information
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
Questions in the test are arranged in blocks. Blocks can be of two types: <strong>Blocks</strong> of multiple related questions, and <strong>Single Questions</strong> that are not part of a block.
|
||||||
|
You can add, remove, or edit both Blockss and Questions through this editor.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Blocks</strong> are useful when you have a section of the test that contains multiple questions that are related to each other, for example if there is a scenario-based section where a series of questions are about the same situation.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Blocks can contain any number of questions within them, but cannot contain nested blocks.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
When you set up a block, you can also add <strong>header text</strong> that will be displayed with each question.
|
||||||
|
You can use this to provide common information for a scenario across a series of questions.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Questions come in three types:
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Yes/No</strong> for when there is only a yes or no option,
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Multiple Choice</strong> for your regular multiple choice questions, and
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Ordered List</strong> for multiple choice questions that will be displayed in the same order as listed here.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Normally, multiple choice questions will have the order of the options randomised.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Questions will be displayed to candidates in a randomised order.
|
||||||
|
Blocks of questions will be kept together, but the order within the block will also be randomised.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Questions can also be categorised using <strong>tags</strong>.
|
||||||
|
</p>
|
||||||
|
<p class="lead">
|
||||||
|
Placeholder for Questions Remaining in a Block
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In order to show how many questions are remaining inside a block, e.g. to say ‘the next n questions are about a specific scenario’, the app uses the placeholder <code><block_remaining_questions></code>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="container viewer-panel">
|
||||||
|
<h3>
|
||||||
|
Question Dataset
|
||||||
|
</h3>
|
||||||
|
<div class="container dataset-metadata">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text">Dataset Name</span>
|
||||||
|
<span class="form-control">
|
||||||
|
{{ dataset.get_name() }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text">Author</span>
|
||||||
|
<span class="form-control">
|
||||||
|
{{ dataset.creator.get_username() }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text">Last Updated</span>
|
||||||
|
<span class="form-control">
|
||||||
|
{{ dataset.date.strftime('%d %b %Y %H:%M') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% if dataset.default %}
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text">
|
||||||
|
<input type="checkbox" aria-label="Default" class="dataset-default" checked disabled>
|
||||||
|
</span>
|
||||||
|
<span class="form-control">
|
||||||
|
Default Dataset
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
<script>
|
||||||
|
const target = "{{ url_for('api._editor') }}"
|
||||||
|
const id = "{{ dataset.id }}"
|
||||||
|
</script>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="{{ url_for('.static', filename='js/view.js') }}"
|
||||||
|
></script>
|
||||||
|
{% endblock %}
|
27
ref-test/app/view/templates/view/index.html
Normal file
27
ref-test/app/view/templates/view/index.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{% extends "view/components/input-forms.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="form-container">
|
||||||
|
<form name="form-editor" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for(request.endpoint, **request.view_args) }}">
|
||||||
|
{% include "admin/components/server-alerts.html" %}
|
||||||
|
<h2 class="form">View Questions</h2>
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
<div class="form-select-input">
|
||||||
|
{{ form.dataset(placeholder="Select Question Dataset") }}
|
||||||
|
{{ form.dataset.label }}
|
||||||
|
</div>
|
||||||
|
{% include "admin/components/client-alerts.html" %}
|
||||||
|
<div class="container form-submission-button">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col text-center">
|
||||||
|
<button class="btn btn-md btn-success btn-block" type="submit">
|
||||||
|
<i class="bi bi-book-fill button-icon"></i>
|
||||||
|
View
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
41
ref-test/app/view/views.py
Normal file
41
ref-test/app/view/views.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
from ..forms.admin import EditDataset
|
||||||
|
from ..models import Dataset, User
|
||||||
|
from ..tools.forms import get_dataset_choices, send_errors_to_client
|
||||||
|
from ..tools.data import check_dataset_exists
|
||||||
|
|
||||||
|
from flask import Blueprint, flash, jsonify, redirect, render_template, request
|
||||||
|
from flask.helpers import url_for
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
|
view = Blueprint(
|
||||||
|
name='view',
|
||||||
|
import_name=__name__,
|
||||||
|
template_folder='templates',
|
||||||
|
static_folder='static'
|
||||||
|
)
|
||||||
|
|
||||||
|
@view.route('/', methods=['GET','POST'])
|
||||||
|
@login_required
|
||||||
|
@check_dataset_exists
|
||||||
|
def _view():
|
||||||
|
form = EditDataset()
|
||||||
|
form.dataset.choices = get_dataset_choices()
|
||||||
|
if request.method == 'POST':
|
||||||
|
if form.validate_on_submit():
|
||||||
|
id = request.form.get('dataset')
|
||||||
|
return jsonify({'success': 'Selected dataset', 'redirect_to': url_for('view._view_console', id=id)}),200
|
||||||
|
return send_errors_to_client(form=form)
|
||||||
|
form.process()
|
||||||
|
return render_template('/view/index.html', form=form)
|
||||||
|
|
||||||
|
@view.route('/<string:id>/')
|
||||||
|
@login_required
|
||||||
|
@check_dataset_exists
|
||||||
|
def _view_console(id:str=None):
|
||||||
|
dataset = Dataset.query.filter_by(id=id).first()
|
||||||
|
datasets = Dataset.query.count()
|
||||||
|
users = User.query.all()
|
||||||
|
if not dataset:
|
||||||
|
flash('Invalid dataset ID.', 'error')
|
||||||
|
return redirect(url_for('admin._questions'))
|
||||||
|
return render_template('/view/console.html', dataset=dataset, datasets=datasets, users=users)
|
Loading…
Reference in New Issue
Block a user