diff --git a/classes/edit_question_form.php b/classes/edit_question_form.php
index 6e10d60a..92098e84 100644
--- a/classes/edit_question_form.php
+++ b/classes/edit_question_form.php
@@ -90,6 +90,36 @@ public function validation($data, $files) {
}
}
+ // If this is a slider question.
+ if ($data['type_id'] == QUESSLIDER) {
+ if (isset($data['minrange']) && isset($data['maxrange']) && isset($data['startingvalue']) &&
+ isset($data['stepvalue'])) {
+ if ($data['minrange'] >= $data['maxrange']) {
+ $errors['maxrange'] = get_string('invalidrange', 'questionnaire');
+ }
+
+ if (($data['startingvalue'] > $data['maxrange']) || ($data['startingvalue'] < $data['minrange'])) {
+ $errors['startingvalue'] = get_string('invalidstartingvalue', 'questionnaire');
+ }
+
+ if ($data['startingvalue'] > 100 || $data['startingvalue'] < -100) {
+ $errors['startingvalue'] = get_string('invalidstartingvalue', 'questionnaire');
+ }
+
+ if (($data['stepvalue'] > $data['maxrange']) || $data['stepvalue'] < 1) {
+ $errors['stepvalue'] = get_string('invalidincrement', 'questionnaire');
+ }
+
+ if ($data['minrange'] < -100) {
+ $errors['minrange'] = get_string('invalidminmaxrange', 'questionnaire');
+ }
+
+ if ($data['maxrange'] > 100) {
+ $errors['maxrange'] = get_string('invalidminmaxrange', 'questionnaire');
+ }
+ }
+ }
+
return $errors;
}
diff --git a/classes/output/mobile.php b/classes/output/mobile.php
index 0dd58aa4..74d3f549 100644
--- a/classes/output/mobile.php
+++ b/classes/output/mobile.php
@@ -258,6 +258,10 @@ protected static function add_pagequestion_data($questionnaire, $pagenum, $respo
$question = $questionnaire->questions[$questionid];
if ($question->supports_mobile()) {
$pagequestions[] = $question->mobile_question_display($qnum, $questionnaire->autonum);
+ $mobileotherdata = $question->mobile_otherdata();
+ if (!empty($mobileotherdata)) {
+ $responses = array_merge($responses, $mobileotherdata);
+ }
if (($response !== null) && isset($response->answers[$questionid])) {
$responses = array_merge($responses, $question->get_mobile_response_data($response));
}
diff --git a/classes/question/question.php b/classes/question/question.php
index b05eab7c..1b649598 100644
--- a/classes/question/question.php
+++ b/classes/question/question.php
@@ -41,6 +41,7 @@
define('QUESRATE', 8);
define('QUESDATE', 9);
define('QUESNUMERIC', 10);
+define('QUESSLIDER', 11);
define('QUESPAGEBREAK', 99);
define('QUESSECTIONTEXT', 100);
@@ -117,7 +118,8 @@ abstract class question {
QUESDATE => 'date',
QUESNUMERIC => 'numerical',
QUESPAGEBREAK => 'pagebreak',
- QUESSECTIONTEXT => 'sectiontext'
+ QUESSECTIONTEXT => 'sectiontext',
+ QUESSLIDER => 'slider',
];
/** @var array $notifications Array of extra messages for display purposes. */
@@ -1563,6 +1565,9 @@ public function mobile_question_display($qnum, $autonum = false) {
];
$mobiledata->choices = $this->mobile_question_choices_display();
+ if ($this->mobile_question_extradata_display()) {
+ $mobiledata->extradata = json_decode($this->extradata);
+ }
if ($autonum) {
$mobiledata->content = $qnum . '. ' . $mobiledata->content;
$mobiledata->content_stripped = $qnum . '. ' . $mobiledata->content_stripped;
@@ -1617,4 +1622,22 @@ public function get_mobile_response_data($response) {
return $resultdata;
}
+
+ /**
+ * True if question need extradata for mobile app.
+ *
+ * @return bool
+ */
+ public function mobile_question_extradata_display() {
+ return false;
+ }
+
+ /**
+ * Return the otherdata to be used by the mobile app.
+ *
+ * @return array
+ */
+ public function mobile_otherdata() {
+ return [];
+ }
}
diff --git a/classes/question/slider.php b/classes/question/slider.php
new file mode 100644
index 00000000..37c284ed
--- /dev/null
+++ b/classes/question/slider.php
@@ -0,0 +1,304 @@
+.
+
+namespace mod_questionnaire\question;
+
+/**
+ * This file contains the parent class for slider question types.
+ *
+ * @author Hieu Vu Van
+ * @copyright 2022 The Open University.
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package mod_questionnaire
+ */
+class slider extends question {
+
+ /**
+ * Return the responseclass used.
+ * @return string
+ */
+ protected function responseclass() {
+ return '\\mod_questionnaire\\responsetype\\slider';
+ }
+
+ /**
+ * Return the help name.
+ * @return string
+ */
+ public function helpname() {
+ return 'slider';
+ }
+
+ /**
+ * Return true if the question has choices.
+ */
+ public function has_choices() {
+ return false;
+ }
+
+ /**
+ * Override and return a form template if provided. Output of question_survey_display is iterpreted based on this.
+ *
+ * @return boolean | string
+ */
+ public function question_template() {
+ return 'mod_questionnaire/question_slider';
+ }
+
+ /**
+ * Override and return a response template if provided. Output of response_survey_display is iterpreted based on this.
+ *
+ * @return boolean | string
+ */
+ public function response_template() {
+ return 'mod_questionnaire/response_slider';
+ }
+
+ /**
+ * Return the context tags for the check question template.
+ *
+ * @param \mod_questionnaire\responsetype\response\response $response
+ * @param array $dependants Array of all questions/choices depending on this question.
+ * @param boolean $blankquestionnaire
+ * @return object The check question context tags.
+ *
+ */
+ protected function question_survey_display($response, $dependants = [], $blankquestionnaire = false) {
+ global $PAGE;
+ $PAGE->requires->js_init_call('M.mod_questionnaire.init_slider', null, false, questionnaire_get_js_module());
+ $extradata = json_decode($this->extradata);
+ $questiontags = new \stdClass();
+ if (isset($response->answers[$this->id][0])) {
+ $extradata->startingvalue = $response->answers[$this->id][0]->value;
+ }
+ $extradata->name = 'q' . $this->id;
+ $extradata->id = self::qtypename($this->type_id) . $this->id;
+ $questiontags->qelements = new \stdClass();
+ $questiontags->qelements->extradata = $extradata;
+ return $questiontags;
+ }
+
+ /**
+ * Return the context tags for the slider response template.
+ * @param \mod_questionnaire\responsetype\response\response $response
+ * @return \stdClass The check question response context tags.
+ */
+ protected function response_survey_display($response) {
+ global $PAGE;
+ $PAGE->requires->js_init_call('M.mod_questionnaire.init_slider', null, false, questionnaire_get_js_module());
+
+ $resptags = new \stdClass();
+ if (isset($response->answers[$this->id])) {
+ $answer = reset($response->answers[$this->id]);
+ $resptags->content = format_text($answer->value, FORMAT_HTML);
+ if (!empty($response->answers[$this->id]['extradata'])) {
+ $resptags->extradata = $response->answers[$this->id]['extradata'];
+ } else {
+ $extradata = json_decode($this->extradata);
+ $resptags->extradata = $extradata;
+ }
+ }
+ return $resptags;
+ }
+
+ /**
+ * Add the form required field.
+ * @param \MoodleQuickForm $mform
+ * @return \MoodleQuickForm
+ */
+ protected function form_required(\MoodleQuickForm $mform) {
+ return $mform;
+ }
+
+ /**
+ * Return the form precision.
+ * @param \MoodleQuickForm $mform
+ * @param string $helptext
+ * @return \MoodleQuickForm|void
+ */
+ protected function form_precise(\MoodleQuickForm $mform, $helptext = '') {
+ return question::form_precise_hidden($mform);
+ }
+
+ /**
+ * Return the form length.
+ * @param \MoodleQuickForm $mform
+ * @param string $helptext
+ * @return \MoodleQuickForm|void
+ */
+ protected function form_length(\MoodleQuickForm $mform, $helptext = '') {
+ return question::form_length_hidden($mform);
+ }
+
+ /**
+ * Override if the question uses the extradata field.
+ * @param \MoodleQuickForm $mform
+ * @param string $helpname
+ * @return \MoodleQuickForm
+ */
+ protected function form_extradata(\MoodleQuickForm $mform, $helpname = '') {
+ $minelementname = 'minrange';
+ $maxelementname = 'maxrange';
+ $startingvalue = 'startingvalue';
+ $stepvalue = 'stepvalue';
+
+ $ranges = [];
+ if (!empty($this->extradata)) {
+ $ranges = json_decode($this->extradata);
+ }
+ $mform->addElement('text', 'leftlabel', get_string('leftlabel', 'questionnaire'));
+ $mform->setType('leftlabel', PARAM_RAW);
+ if (isset($ranges->leftlabel)) {
+ $mform->setDefault('leftlabel', $ranges->leftlabel);
+ }
+ $mform->addElement('text', 'centerlabel', get_string('centerlabel', 'questionnaire'));
+ $mform->setType('centerlabel', PARAM_RAW);
+ if (isset($ranges->centerlabel)) {
+ $mform->setDefault('centerlabel', $ranges->centerlabel);
+ }
+ $mform->addElement('text', 'rightlabel', get_string('rightlabel', 'questionnaire'));
+ $mform->setType('rightlabel', PARAM_RAW);
+ if (isset($ranges->rightlabel)) {
+ $mform->setDefault('rightlabel', $ranges->rightlabel);
+ }
+
+ $patterint = '/^-?\d+$/';
+ $mform->addElement('text', $minelementname, get_string($minelementname, 'questionnaire'), ['size' => '3']);
+ $mform->setType($minelementname, PARAM_RAW);
+ $mform->addRule($minelementname, get_string('err_required', 'form'), 'required', null, 'client');
+ $mform->addRule($minelementname, get_string('err_numeric', 'form'), 'numeric', '', 'client');
+ $mform->addRule($minelementname, get_string('err_numeric', 'form'), 'regex', $patterint, 'client');
+ $mform->addHelpButton($minelementname, $minelementname, 'questionnaire');
+ if (isset($ranges->minrange)) {
+ $mform->setDefault($minelementname, $ranges->minrange);
+ } else {
+ $mform->setDefault($minelementname, 1);
+ }
+
+ $mform->addElement('text', $maxelementname, get_string($maxelementname, 'questionnaire'), ['size' => '3']);
+ $mform->setType($maxelementname, PARAM_RAW);
+ $mform->addHelpButton($maxelementname, $maxelementname, 'questionnaire');
+ $mform->addRule($maxelementname, get_string('err_required', 'form'), 'required', null, 'client');
+ $mform->addRule($maxelementname, get_string('err_numeric', 'form'), 'numeric', '', 'client');
+ $mform->addRule($maxelementname, get_string('err_numeric', 'form'), 'regex', $patterint, 'client');
+ if (isset($ranges->maxrange)) {
+ $mform->setDefault($maxelementname, $ranges->maxrange);
+ } else {
+ $mform->setDefault($maxelementname, 10);
+ }
+
+ $mform->addElement('text', $startingvalue, get_string($startingvalue, 'questionnaire'), ['size' => '3']);
+ $mform->setType($startingvalue, PARAM_RAW);
+ $mform->addHelpButton($startingvalue, $startingvalue, 'questionnaire');
+ $mform->addRule($startingvalue, get_string('err_required', 'form'), 'required', null, 'client');
+ $mform->addRule($startingvalue, get_string('err_numeric', 'form'), 'numeric', '', 'client');
+ $mform->addRule($startingvalue, get_string('err_numeric', 'form'), 'regex', $patterint, 'client');
+ if (isset($ranges->startingvalue)) {
+ $mform->setDefault($startingvalue, $ranges->startingvalue);
+ } else {
+ $mform->setDefault($startingvalue, 5);
+ }
+
+ $mform->addElement('text', $stepvalue, get_string($stepvalue, 'questionnaire'), ['size' => '3']);
+ $mform->setType($stepvalue, PARAM_RAW);
+ $mform->addHelpButton($stepvalue, $stepvalue, 'questionnaire');
+ $mform->addRule($stepvalue, get_string('err_required', 'form'), 'required', null, 'client');
+ $mform->addRule($stepvalue, get_string('err_numeric', 'form'), 'numeric', '', 'client');
+ $mform->addRule($stepvalue, get_string('err_numeric', 'form'), 'regex', '/^-?\d+$/', 'client');
+
+ if (isset($ranges->stepvalue)) {
+ $mform->setDefault($stepvalue, $ranges->stepvalue);
+ } else {
+ $mform->setDefault($stepvalue, 1);
+ }
+ return $mform;
+ }
+
+ /**
+ * Any preprocessing of general data.
+ * @param \stdClass $formdata
+ * @return bool
+ */
+ protected function form_preprocess_data($formdata) {
+ $ranges = [];
+ if (isset($formdata->minrange)) {
+ $ranges['minrange'] = $formdata->minrange;
+ }
+ if (isset($formdata->maxrange)) {
+ $ranges['maxrange'] = $formdata->maxrange;
+ }
+ if (isset($formdata->startingvalue)) {
+ $ranges['startingvalue'] = $formdata->startingvalue;
+ }
+ if (isset($formdata->stepvalue)) {
+ $ranges['stepvalue'] = $formdata->stepvalue;
+ }
+ if (isset($formdata->leftlabel)) {
+ $ranges['leftlabel'] = $formdata->leftlabel;
+ }
+ if (isset($formdata->rightlabel)) {
+ $ranges['rightlabel'] = $formdata->rightlabel;
+ }
+ if (isset($formdata->centerlabel)) {
+ $ranges['centerlabel'] = $formdata->centerlabel;
+ }
+
+ // Now store the new named degrees in extradata.
+ $formdata->extradata = json_encode($ranges);
+ return parent::form_preprocess_data($formdata);
+ }
+
+ /**
+ * True if question provides mobile support.
+ *
+ * @return bool
+ */
+ public function supports_mobile() {
+ return true;
+ }
+
+ /**
+ * True if question need extradata for mobile app.
+ *
+ * @return bool
+ */
+ public function mobile_question_extradata_display() {
+ return true;
+ }
+
+ /**
+ * Return the mobile question display.
+ *
+ * @param int $qnum
+ * @param bool $autonum
+ * @return \stdClass
+ */
+ public function mobile_question_display($qnum, $autonum = false) {
+ $mobiledata = parent::mobile_question_display($qnum, $autonum);
+ $mobiledata->isslider = true;
+ return $mobiledata;
+ }
+
+ /**
+ * Return the otherdata to be used by the mobile app.
+ *
+ * @return array
+ */
+ public function mobile_otherdata() {
+ $extradata = json_decode($this->extradata);
+ return [$this->mobile_fieldkey() => $extradata->startingvalue];
+ }
+}
diff --git a/classes/questions_form.php b/classes/questions_form.php
index 40966eb2..769cbe86 100644
--- a/classes/questions_form.php
+++ b/classes/questions_form.php
@@ -256,7 +256,7 @@ public function definition() {
$manageqgroup[] =& $mform->createElement('image', 'editbutton['.$question->id.']', $esrc, $eextra);
$manageqgroup[] =& $mform->createElement('image', 'removebutton['.$question->id.']', $rsrc, $rextra);
- if ($tid != QUESPAGEBREAK && $tid != QUESSECTIONTEXT) {
+ if ($tid != QUESPAGEBREAK && $tid != QUESSECTIONTEXT && $tid != QUESSLIDER) {
if ($required == 'y') {
$reqsrc = $questionnaire->renderer->image_url('t/stop');
$strrequired = get_string('required', 'questionnaire');
diff --git a/classes/responsetype/slider.php b/classes/responsetype/slider.php
new file mode 100644
index 00000000..5055c3d1
--- /dev/null
+++ b/classes/responsetype/slider.php
@@ -0,0 +1,54 @@
+.
+
+namespace mod_questionnaire\responsetype;
+
+/**
+ * Class for slider text response types.
+ *
+ * @author Hieu Vu Van
+ * @copyright 2022 The Open University.
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package mod_questionnaire
+ */
+class slider extends numericaltext {
+ /**
+ * Return an array of answer objects by question for the given response id.
+ * THIS SHOULD REPLACE response_select.
+ *
+ * @param int $rid The response id.
+ * @return array array answer
+ * @throws \dml_exception
+ */
+ public static function response_answers_by_question($rid) {
+ global $DB;
+
+ $answers = [];
+ $sql = 'SELECT qs.id, qs.response_id as responseid, qs.question_id as questionid,
+ 0 as choiceid, qs.response as value, qq.extradata ' .
+ 'FROM {' . static::response_table() . '} qs ' .
+ 'INNER JOIN {questionnaire_question} qq ON qq.id = qs.question_id ' .
+ 'WHERE response_id = ? ';
+ $records = $DB->get_records_sql($sql, [$rid]);
+ foreach ($records as $record) {
+ $answers[$record->questionid][] = answer\answer::create_from_data($record);
+ if (!empty($record->extradata)) {
+ $answers[$record->questionid]['extradata'] = json_decode($record->extradata);
+ }
+ }
+ return $answers;
+ }
+}
diff --git a/db/install.php b/db/install.php
index 2e630363..55b0eda4 100644
--- a/db/install.php
+++ b/db/install.php
@@ -93,6 +93,13 @@ function xmldb_questionnaire_install() {
$questiontype->response_table = 'response_text';
$id = $DB->insert_record('questionnaire_question_type', $questiontype);
+ $questiontype = new stdClass();
+ $questiontype->typeid = 11;
+ $questiontype->type = 'Slider';
+ $questiontype->has_choices = 'n';
+ $questiontype->response_table = 'response_text';
+ $id = $DB->insert_record('questionnaire_question_type', $questiontype);
+
$questiontype = new stdClass();
$questiontype->typeid = 99;
$questiontype->type = 'Page Break';
diff --git a/db/mobile.php b/db/mobile.php
index cbc7dcef..01bec8fb 100644
--- a/db/mobile.php
+++ b/db/mobile.php
@@ -36,7 +36,7 @@
'method' => 'mobile_view_activity',
'styles' => [
'url' => $CFG->wwwroot . '/mod/questionnaire/styles_app.css',
- 'version' => '1.4'
+ 'version' => '1.5'
]
]
],
diff --git a/db/upgrade.php b/db/upgrade.php
index daf754d2..3861f365 100644
--- a/db/upgrade.php
+++ b/db/upgrade.php
@@ -983,6 +983,20 @@ function xmldb_questionnaire_upgrade($oldversion=0) {
upgrade_mod_savepoint(true, 2020062301, 'questionnaire');
}
+ if ($oldversion < 2022092200) {
+ // Add new slider question type.
+ $exist = $DB->record_exists('questionnaire_question_type', ['typeid' => 11]);
+ if (!$exist) {
+ $questiontype = new stdClass();
+ $questiontype->typeid = 11;
+ $questiontype->type = 'Slider';
+ $questiontype->has_choices = 'n';
+ $questiontype->response_table = 'response_text';
+ $DB->insert_record('questionnaire_question_type', $questiontype);
+ }
+ upgrade_mod_savepoint(true, 2022092200, 'questionnaire');
+ }
+
return $result;
}
diff --git a/lang/en/questionnaire.php b/lang/en/questionnaire.php
index d904e11b..dce6e767 100644
--- a/lang/en/questionnaire.php
+++ b/lang/en/questionnaire.php
@@ -117,6 +117,7 @@
$string['createcontent_help'] = 'Select one of the radio button options. \'Create new\' is the default.';
$string['createcontent_link'] = 'mod/questionnaire/mod#Content_Options';
$string['createnew'] = 'Create new';
+$string['centerlabel'] = 'Centre label';
$string['date'] = 'Date';
$string['date_help'] = 'Use this question type if you expect the response to be a correctly formatted date.';
$string['date_link'] = 'mod/questionnaire/questions#Date';
@@ -263,12 +264,17 @@
$string['invalidresponserecord'] = 'Invalid response record specified.';
$string['invalidsurveyid'] = 'Invalid questionnaire ID.';
$string['invalidsectionid'] = 'Invalid feedback section specified.';
+$string['invalidrange'] = 'The maximum slider value must be greater than the minimum slider value.';
+$string['invalidstartingvalue'] = 'The starting value must be equal to or between the minimum and maximum values. For example, if using a scale of 1-10, the starting value could be 5.';
+$string['invalidminmaxrange'] = 'This question type supports an absolute maximum range of -100 to +100. We expect the vast majority of questionnaire designs to use a range of 1-10 or -10 to +10.';
+$string['invalidincrement'] = 'Note that the value increments must be lower than the maximum value. For example, if a scale of 1-10, the increment value would probably be 1.';
$string['indirectwarnings'] = 'This list shows the indirect dependent questions and the remaining dependencies for direct dependent questions:';
$string['kindofratescale'] = 'Type of rate scale';
$string['kindofratescale_help'] = 'Right-click on the More Help link below.';
$string['kindofratescale_link'] = 'mod/questionnaire/questions#Type_of_rate_scale';
$string['lastrespondent'] = 'Last Respondent';
$string['length'] = 'Length';
+$string['leftlabel'] = 'Left label';
$string['managequestions'] = 'Manage questions';
$string['managequestions_help'] = 'In the Manage questions section of the Edit Questions page, you can conduct a number of operations on a Questionnaire\'s questions.';
$string['managequestions_link'] = 'mod/questionnaire/questions#Manage_questions';
@@ -305,6 +311,10 @@
$string['myresponses'] = 'All your responses';
$string['myresponsetitle'] = 'Your {$a} response(s)';
$string['myresults'] = 'Your Results';
+$string['minrange'] = 'Minimum slider range (left)';
+$string['minrange_help'] = 'Set the minimum value of the range on the left-hand side. It defaults to 1, but can set as low as -100. If you use a negative number (-100 to -1), the right-hand maximum will be expressed with a positive (+) sign.';
+$string['maxrange'] = 'Maximum slider range (right)';
+$string['maxrange_help'] = 'Set the maximum value of the range on the right-hand side. It defaults to 100, but it could be any number between 1-100. If the minimum value for the left-hand is a negative value, the maximum range will be expressed with a positive (+) sign.';
$string['name'] = 'Name';
$string['navigate'] = 'Allow branching questions';
$string['navigate_help'] = 'Enable Yes/No and Radio Buttons questions to have Child questions dependent on their choices in your questionnaire.';
@@ -548,6 +558,7 @@
$string['resume_link'] = 'mod/questionnaire/mod#Save/Resume_answers';
$string['resumesurvey'] = 'Resume questionnaire';
$string['return'] = 'Return';
+$string['rightlabel'] = 'Right label';
$string['save'] = 'Save';
$string['save_and_exit'] = 'Save and exit';
$string['saveasnew'] = 'Save as New Question';
@@ -596,6 +607,12 @@
$string['surveynotexists'] = 'questionnaire does not exist.';
$string['surveyowner'] = 'You must be a questionnaire owner to perform this operation.';
$string['surveyresponse'] = 'Response from questionnaire';
+$string['slider'] = 'Slider';
+$string['slider_help'] = 'The slider question allows respondents to select a value from a continuous range by dragging a slider between two extremes. A centre value can also be set.';
+$string['startingvalue'] = 'Slider starting value';
+$string['startingvalue_help'] = 'The slider starting value specifies where the slider should first appear for respondents. It defaults to 1 because the range is unknown. You may wish to start it in the centre of the range by giving a central value (a range of 1-100 has a centre value of 50).';
+$string['stepvalue'] = 'Slider increment value';
+$string['stepvalue_help'] = 'The slider increment value specifies how finely you wish respondents to indicate their response in the range. The question defaults to a range of 1-100 with an increment of one, allowing respondents to give values of 70, 71, 72, 73, 74 etc. But you could instead set increments of five, allowing respondents to give values of 60, 65, 70, 75, 80 etc., or even just a range of 1-10 with increments of 1.';
$string['template'] = 'Template';
$string['templatenotviewable'] = 'Template questionnaires are not viewable.';
$string['text'] = 'Question Text';
diff --git a/locallib.php b/locallib.php
index f4f7b932..8875a2ce 100644
--- a/locallib.php
+++ b/locallib.php
@@ -487,6 +487,8 @@ function questionnaire_get_type ($id) {
return get_string('date', 'questionnaire');
case 10:
return get_string('numeric', 'questionnaire');
+ case 11:
+ return get_string('slider', 'questionnaire');
case 100:
return get_string('sectiontext', 'questionnaire');
case 99:
diff --git a/module.js b/module.js
index 28d7b628..f3eb8d60 100644
--- a/module.js
+++ b/module.js
@@ -241,4 +241,35 @@ M.mod_questionnaire.init_sendmessage = function(Y) {
});
}, '#checkstarted');
-};
\ No newline at end of file
+};
+M.mod_questionnaire.init_slider = function(Y) {
+ const allRanges = document.querySelectorAll(".slider");
+ allRanges.forEach(wrap => {
+ const range = wrap.querySelector("input.questionnaire-slider");
+ const bubble = wrap.querySelector(".bubble");
+
+ range.addEventListener("input", () => {
+ setBubble(range, bubble);
+ });
+ setBubble(range, bubble);
+ });
+
+ function setBubble(range, bubble) {
+ const val = range.value;
+ const min = range.min ? range.min : 0;
+ const max = range.max ? range.max : 100;
+ var newVal = Number(((val - min) * 100) / (max - min));
+ var positiveVal = '';
+ if (range.min && range.min < 0) {
+ if (range.max && range.max > 0) {
+ if (val > 0) {
+ positiveVal = '+';
+ }
+ }
+ }
+ bubble.innerHTML = positiveVal + val;
+
+ // Sorta magic numbers based on size of the native UI thumb
+ bubble.style.left = `calc(${newVal}% + (${8 - newVal * 0.15}px))`;
+ }
+};
diff --git a/questionnaire.class.php b/questionnaire.class.php
index ef599ca4..e677636d 100644
--- a/questionnaire.class.php
+++ b/questionnaire.class.php
@@ -3245,7 +3245,8 @@ public function generate_csv($currentgroupid, $rid='', $userid='', $choicecodes=
'0', // 7: rating -> number
'0', // 8: rate -> number
'1', // 9: date -> string
- '0' // 10: numeric -> number.
+ '0', // 10: numeric -> number.
+ '0', // 11: slider -> number.
);
if (!$survey = $DB->get_record('questionnaire_survey', array('id' => $this->survey->id))) {
@@ -4036,7 +4037,7 @@ public function save_mobile_data($userid, $sec, $completed, $rid, $submit, $acti
global $DB, $CFG; // Do not delete "$CFG".
$ret = [];
- $response = $this->build_response_from_appdata($responses, $sec);
+ $response = $this->build_response_from_appdata((object)$responses, $sec);
$response->sec = $sec;
$response->rid = $rid;
$response->id = $rid;
diff --git a/styles.css b/styles.css
index 28f45a3c..41dcd324 100644
--- a/styles.css
+++ b/styles.css
@@ -453,4 +453,93 @@ td.selected {
#page-mod-questionnaire-questions #fitem_id_allchoices #id_allchoices,
#page-mod-questionnaire-questions #fitem_id_allnameddegrees #id_allnameddegrees {
resize: both;
-}
\ No newline at end of file
+}
+
+.path-mod-questionnaire .slidecontainer {
+ width: 100%;
+}
+
+.path-mod-questionnaire .slider {
+ -webkit-appearance: none;
+ width: 100%;
+ outline: none;
+ opacity: 0.7;
+ -webkit-transition: .2s;
+ transition: opacity .2s;
+ float: left;
+ margin-top: 40px;
+}
+.path-mod-questionnaire .slider input {
+ width: 100%;
+}
+
+.path-mod-questionnaire .slider:hover {
+ opacity: 1;
+}
+
+.path-mod-questionnaire .slider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 100%;
+ height: 25px;
+ background: #04aa6d;
+ cursor: pointer;
+ border-radius: 50%;
+}
+
+.path-mod-questionnaire .slider::-moz-range-thumb {
+ width: 25px;
+ height: 25px;
+ background: #04aa6d;
+ cursor: pointer;
+}
+
+.path-mod-questionnaire .question-slider {
+ display: flex;
+ align-items: baseline;
+}
+
+.path-mod-questionnaire .left-side-label {
+ text-align: right;
+ padding-right: 20px;
+ margin-top: 40px;
+ flex-grow: 1;
+}
+
+.path-mod-questionnaire .right-side-label {
+ text-align: left;
+ padding-left: 20px;
+ margin-top: 40px;
+ flex-grow: 1;
+}
+
+.path-mod-questionnaire .middle-side-content {
+ flex-grow: 8;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.path-mod-questionnaire .middle-side-label {
+ text-align: center;
+}
+
+.path-mod-questionnaire .bubble {
+ background: #000;
+ color: white;
+ padding: 3px;
+ border-radius: 10px;
+ left: 50%;
+ transform: translate(-52%, -50px);
+ position: relative;
+ text-align: center;
+ width: 40px;
+}
+.path-mod-questionnaire .bubble::after {
+ content: "";
+ position: absolute;
+ width: 2px;
+ height: 2px;
+ left: 50%;
+}
diff --git a/styles_app.css b/styles_app.css
index 925f2ed4..e1b368db 100644
--- a/styles_app.css
+++ b/styles_app.css
@@ -1,4 +1,17 @@
span.mobileratequestion {
padding-left: 2em;
padding-right: 2em;
+}
+
+.mod_questionnaire_slider .range-has-pin .range-pin {
+ -webkit-transform: translate3d(0, 0, 0) scale(1);
+ transform: translate3d(0, 0, 0) scale(1);
+}
+.mod_questionnaire_slider .range-has-pin::part(pin){
+ -webkit-transform: translate3d(0, -24px, 0) scale(1);
+ transform: translate3d(0, -24px, 0) scale(1);
+}
+
+ion-label.disabled {
+ opacity: 0.8 !important;
}
\ No newline at end of file
diff --git a/templates/local/mobile/ionic3/slider_question.mustache b/templates/local/mobile/ionic3/slider_question.mustache
new file mode 100644
index 00000000..64aef335
--- /dev/null
+++ b/templates/local/mobile/ionic3/slider_question.mustache
@@ -0,0 +1,55 @@
+{{!
+ 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