Skip to content

Commit

Permalink
Merge pull request #124 from KQMATH/development
Browse files Browse the repository at this point in the history
v0.2 release
  • Loading branch information
andstor authored Jun 24, 2019
2 parents 3ac6489 + cbbeb41 commit c5f0df4
Show file tree
Hide file tree
Showing 48 changed files with 1,470 additions and 298 deletions.
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Changed
-

## [0.2.0] - 2019-06-24
### Added
* Questions can now be sorted by name and rating
* Students can now comment on questions to give feedback to the instructor
* Added tooltips to stars to inform what they mean
* GDPR compliance
* Ability to import from question list templates
* Ability to delete question list templates
* Added "Edit" and "Preview" button to questions in Edit tab

### Changed
* Better star visualization (dimmed star to show "lost" stars)
* Some style improvements to Edit tab

### Fixed
* Various bugfixes

## [0.1.5] - 2019-03-08
### Fixed
* Fix language file to support AMOS translation system.
Expand All @@ -28,8 +45,9 @@ and this project adheres to [Semantic Versioning](https://semver.org).

## 0.1.0 - 2018-09-28

[Unreleased]: https://github.com/KQMATH/moodle-mod_capquiz/compare/v0.1.5...HEAD
[Unreleased]: https://github.com/KQMATH/moodle-mod_capquiz/compare/v0.2.0...HEAD

[0.2.0]: https://github.com/KQMATH/moodle-mod_capquiz/compare/v0.1.5...v0.2.0
[0.1.5]: https://github.com/KQMATH/moodle-mod_capquiz/compare/v0.1.4...v0.1.5
[0.1.4]: https://github.com/KQMATH/moodle-mod_capquiz/compare/v0.1.3...v0.1.4
[0.1.3]: https://github.com/KQMATH/moodle-mod_capquiz/compare/v0.1.2...v0.1.3
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ The idea of an adaptive learning system at NTNU in Ålesund (then Ålesund Unive
The first prototype was tested by several lecturers, and was well received by students. There were, however, many problems which we lacked the resources to handle. Most of these problems had already been solved by Moodle and the STACK question type, and it made sense to reimplement the adaptive quiz functionality in Moodle to take advantage of this.

## Credits:
CAPQuiz includes the work of many [contributors](https://github.com/KQMATH/moodle-mod_capquiz/wiki/Credits).

**Project lead:** Hans Georg Schaathun: <hasc@ntnu.no>

**Developers:**
Expand Down
1 change: 1 addition & 0 deletions amd/build/attempt.min.js

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

2 changes: 1 addition & 1 deletion amd/build/edit_questions.min.js

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

87 changes: 87 additions & 0 deletions amd/src/attempt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* @package mod_capquiz
* @author Sebastian S. Gundersen <sebastian@sgundersen.com>
* @copyright 2019 NTNU
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

define(['jquery', 'core/str'], function($, mString) {

/**
*
* @param $form
*/
function moveCommentFieldToForm($form) {
var $comment = $('.capquiz-student-comment');
if ($comment.find('textarea').val().length) {
$comment.prop('open', true);
}
$form.prepend($comment);
}

function showTooltip($element, text) {
var $tooltip = $('.capquiz-star-tooltip');
$tooltip.html(text);
$tooltip.css('display', 'block');
var x = $element.offset().left - $tooltip.width() / 2;
var y = $element.offset().top + 32;
$tooltip.css('left', x + 'px');
$tooltip.css('top', y + 'px');
}

function hideTooltip() {
$('.capquiz-star-tooltip').css('display', 'none');
}

function enableTooltips() {
$(document).on('mouseover', '.capquiz-quiz-stars span', function () {
var $self = $(this);
if ($self.hasClass('capquiz-star')) {
$.when(mString.get_string('tooltip_achieved_star', 'capquiz')).done(function (text) {
showTooltip($self, text);
});
} else if ($self.hasClass('capquiz-lost-star')) {
$.when(mString.get_string('tooltip_lost_star', 'capquiz')).done(function (text) {
showTooltip($self, text);
});
} else if ($self.hasClass('capquiz-no-star')) {
$.when(mString.get_string('tooltip_no_star', 'capquiz')).done(function (text) {
showTooltip($self, text);
});
} else if ($self.hasClass('capquiz-help-stars')) {
$.when(mString.get_string('tooltip_help_star', 'capquiz')).done(function (text) {
showTooltip($self, text);
});
}
});
$(document).on('mouseleave', '.capquiz-quiz-stars span', function () {
hideTooltip();
});
}

return {
initialize: function() {
var $nextButton = $('#capquiz_review_next');
if ($nextButton.length) {
moveCommentFieldToForm($nextButton.parent());
}
enableTooltips();
}
};

});
153 changes: 139 additions & 14 deletions amd/src/edit_questions.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,67 @@ define(['jquery'], function($) {
capquizId: 0,
};

function sendQuestionRating(questionId, rating, onSuccess, onError) {
/**
* Send an action to the server.
* @param data
* @param onSuccess
* @param onError
*/
function sendAction(data, onSuccess, onError) {
$.ajax({
type: 'post',
url: 'action.php',
data: {
'action': 'set-question-rating',
'id': parameters.capquizId,
'question-id': questionId,
'rating': rating,
},
data: data,
success: onSuccess,
error: onError
});
}

function submitQuestionRating($input) {
/**
* Send the new default rating for the question list to the server.
* @param {object} data
* @param {number} rating
* @param {callback} onSuccess
* @param {callback} onError
*/
function sendDefaultQuestionRating(data, rating, onSuccess, onError) {
sendAction({
'action': 'set-default-question-rating',
'id': parameters.capquizId,
'rating': rating,
}, onSuccess, onError);
}

/**
* Send the new rating for the question to the server.
* @param {object} data
* @param {number} rating
* @param {callback} onSuccess
* @param {callback} onError
*/
function sendQuestionRating(data, rating, onSuccess, onError) {
sendAction({
'action': 'set-question-rating',
'id': parameters.capquizId,
'question-id': data.questionId,
'rating': rating,
}, onSuccess, onError);
}

/**
* Send the new value, and avoid race condition.
* @param {object} $input
* @param {callback} sendInput
* @param {object} data
*/
function submitInput($input, sendInput, data) {
$input.data('saving', true);
$input.data('dirty', false);
var $indicator = $input.next();
$indicator.css('color', 'blue');
sendQuestionRating($input.data('question-id'), $input.val(), function() {
sendInput(data, $input.val(), function() {
if ($input.data('dirty') === true) {
submitQuestionRating($input);
submitInput($input, sendInput, data);
} else {
$indicator.css('color', 'green');
$input.data('dirty', false);
Expand All @@ -59,18 +97,103 @@ define(['jquery'], function($) {
});
}

function registerQuestionRatingListeners() {
$(document).on('input', '.capquiz-question-rating input', function(event) {
/**
* Send the new rating for the question, and avoid race condition.
* @param $input
*/
function submitQuestionRating($input) {
submitInput($input, sendQuestionRating, {questionId: $input.data('question-id')});
}

/**
* Send the new default rating for the question list, and avoid race condition.
* @param $input
*/
function submitDefaultQuestionRating($input) {
submitInput($input, sendDefaultQuestionRating, null);
}

/**
* Register an input event listener for submission.
* @param {string} query
* @param {callback} submit
*/
function registerListener(query, submit) {
$(document).on('input', query, function(event) {
var $input = $(event.target);
var isBeingSaved = $input.data('saving');
if (isBeingSaved === true) {
$input.data('dirty', true);
return;
}
submitQuestionRating($input);
submit($input);
});
}

/**
* Sorts a table by the respective column based on $header.
* It searches for an element of class "capquiz-sortable-item" inside the <td>, and if found,
* the value attribute is used if it exists. Otherwise, the inner html is used to sort by.
*
* The <td> tag may not have the item class, as it has no effect on the sorting.
* Their children elements are not required to have the class either. The inner html of <td> will be used then.
*
* The first column in the table must be an index of the row.
*
* @param $header The header column for which to sort the table by.
*/
function sortTable($header) {
var column = $header.index();
var $table = $header.parent().parent();
var $rows = $table.find('tr:gt(0)').toArray().sort(function (rowA, rowB) {
var $colA = $(rowA).children('td').eq(0);
var $colB = $(rowB).children('td').eq(0);
return parseInt($colA.text()) - parseInt($colB.text());
});
$table.append($rows);
$rows = $table.find('tr:gt(0)').toArray().sort(function (rowA, rowB) {
var $colA = $(rowA).children('td').eq(column);
var $colB = $(rowB).children('td').eq(column);
var $itemA = $colA.find('.capquiz-sortable-item');
var $itemB = $colB.find('.capquiz-sortable-item');
var valA = ($itemA.length === 0 ? $colA.html() : ($itemA.val().length === 0 ? $itemA.html() : $itemA.val()));
var valB = ($itemB.length === 0 ? $colB.html() : ($itemB.val().length === 0 ? $itemB.html() : $itemB.val()));
if ($.isNumeric(valA) && $.isNumeric(valB)) {
return valA - valB;
} else {
return valA.toString().localeCompare(valB);
}
});
var ascending = ($table.data('asc') === 'true');
$table.data('asc', ascending ? 'false' : 'true');
var iconName = (ascending ? 'fa-arrow-up' : 'fa-arrow-down');
$.each($table.find('.capquiz-sortable'), function () {
$(this).find('.fa').remove();
});
$header.prepend('<i class="fa ' + iconName + '"></i>');
if (!ascending) {
$rows = $rows.reverse();
}
$table.append($rows);
var i = 1;
$table.find('tr:gt(0)').each(function () {
$(this).find('td:first-child').html(i);
i++;
});
}

/**
* Register click event listeners for the sortable table columns.
*/
function registerSortListener() {
$(document).on('click', '.capquiz-sortable', function() {
sortTable($(this));
});
}

/**
* Set the tab indices for the question rating elements to be more user friendly.
*/
function fixTabIndicesForQuestionRatingInputs() {
$('.capquiz-question-rating-submit-wrapper button').each(function(index, object) {
$(object).attr('tabindex', -1);
Expand All @@ -80,8 +203,10 @@ define(['jquery'], function($) {
return {
initialize: function(capquizId) {
parameters.capquizId = capquizId;
registerQuestionRatingListeners();
registerListener('.capquiz-question-rating input', submitQuestionRating);
registerListener('.capquiz-default-question-rating input', submitDefaultQuestionRating);
fixTabIndicesForQuestionRatingInputs();
registerSortListener();
}
};

Expand Down
2 changes: 2 additions & 0 deletions async.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@

$action = required_param(capquiz_actions::$parameter, PARAM_TEXT);
$attemptid = optional_param(capquiz_urls::$paramattempt, null, PARAM_INT);
$comment = optional_param('studentcomment', '', PARAM_TEXT);
$capquiz = capquiz::create();

capquiz_urls::set_page_url($capquiz, capquiz_urls::$urlasync);

if ($attemptid !== null) {
$user = $capquiz->user();
$attempt = capquiz_question_attempt::load_attempt($capquiz, $user, $attemptid);
$attempt->update_student_comment($comment);
if ($action === capquiz_actions::$attemptanswered) {
$capquiz->question_engine()->attempt_answered($user, $attempt);
} else if ($action === capquiz_actions::$attemptreviewed) {
Expand Down
2 changes: 1 addition & 1 deletion backup/moodle2/backup_capquiz_stepslib.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ protected function define_structure() {
]);
$attempts = new backup_nested_element('attempts');
$attempt = new backup_nested_element('attempt', ['id'], [
'slot', 'user_id', 'question_id', 'reviewed', 'answered', 'time_answered', 'time_reviewed'
'slot', 'user_id', 'question_id', 'reviewed', 'answered', 'time_answered', 'time_reviewed', 'feedback'
]);

// Build the tree.
Expand Down
1 change: 1 addition & 0 deletions backup/moodle2/restore_capquiz_stepslib.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ protected function inform_new_usage_id($newusageid) {
$data = $this->currentquestionlist;
$oldid = $data->id;
$data->question_usage_id = $newusageid;
$data->context_id = \context_course::instance($this->get_courseid())->id;
$data->capquiz_id = $this->get_new_parentid('capquiz');
$newitemid = $DB->insert_record('capquiz_question_list', $data);
$this->set_mapping('capquiz_question_list', $oldid, $newitemid);
Expand Down
Loading

0 comments on commit c5f0df4

Please sign in to comment.