Skip to content

Commit

Permalink
Port question editor unique subquestion code fix from LS6 (#3773)
Browse files Browse the repository at this point in the history
* Dev: Port question editor unique subquestion code fix from LS6
* Dev: Look up existing subquestion by id rather than by title/code
  • Loading branch information
kevin-foster-uk committed Mar 6, 2024
1 parent 7ceb558 commit 4ac4251
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 39 deletions.
55 changes: 44 additions & 11 deletions application/controllers/QuestionAdministrationController.php
Expand Up @@ -195,6 +195,7 @@ public function renderFormAux(Question $question)
$jsVariablesHtml = $this->renderPartial(
'/admin/survey/Question/_subQuestionsAndAnwsersJsVariables',
[
'qid' => $question->qid,
'anslangs' => $question->survey->allLanguages,
// TODO
'assessmentvisible' => false,
Expand Down Expand Up @@ -267,6 +268,36 @@ public function renderFormAux(Question $question)
);
}

/**
* @param int $questionId
* @return void
*/
public function actionAjaxLoadExtraOptions($questionId)
{
$questionId = (int) $questionId;
$question = Question::model()->findByPk($questionId);
if (empty($question)) {
throw new CHttpException(404, gT('Invalid question id'));
}

if (!Permission::model()->hasSurveyPermission($question->sid, 'surveycontent', 'read')) {
Yii::app()->user->setFlash('error', gT("Access denied"));
$this->redirect(Yii::app()->request->urlReferrer);
}
Yii::app()->loadHelper("admin.htmleditor");
PrepareEditorScript(false, $this);
App()->session['FileManagerContext'] = "edit:survey:{$question->sid}";
initKcfinder();

$this->renderPartial(
'extraOptions',
[
'question' => $question,
'survey' => $question->survey,
]
);
}

/**
* Load list questions view for a specified survey by $surveyid
*
Expand Down Expand Up @@ -1710,6 +1741,7 @@ public function actionCopyQuestion()
$aData['jsVariablesHtml'] = $this->renderPartial(
'/admin/survey/Question/_subQuestionsAndAnwsersJsVariables',
[
'qid' => $oQuestion->qid,
'anslangs' => $oQuestion->survey->allLanguages,
// TODO
'assessmentvisible' => false,
Expand Down Expand Up @@ -2769,7 +2801,7 @@ private function storeSubquestions($question, $subquestionsArray)
{
$questionOrder = 0;
$subquestionIds = [];
foreach ($subquestionsArray as $subquestionArray) {
foreach ($subquestionsArray as $subquestionId => $subquestionArray) {
foreach ($subquestionArray as $scaleId => $data) {
if (!isset($data['code'])) {
throw new CHttpException(
Expand All @@ -2778,12 +2810,12 @@ private function storeSubquestions($question, $subquestionsArray)
);
}
$subquestion = null;
if (isset($data['oldcode'])) {
if (is_numeric($subquestionId)) {
$subquestion = Question::model()->findByAttributes([
'qid' => $subquestionId,
'sid' => $question->sid,
'parent_qid' => $question->qid,
'scale_id' => $scaleId,
'title' => $data['oldcode']
'scale_id' => $scaleId
]);
}
if (!$subquestion) {
Expand All @@ -2792,13 +2824,17 @@ private function storeSubquestions($question, $subquestionsArray)
$subquestion->sid = $question->sid;
$subquestion->gid = $question->gid;
$subquestion->parent_qid = $question->qid;
if ($scaleId === 0) {
$subquestion->relevance = array_key_exists(
'relevance',
$data
) ? $data['relevance'] : null;
}
$subquestion->scale_id = $scaleId;
$subquestion->question_order = $questionOrder;
$questionOrder++;
$subquestion->title = $data['code'];
if ($scaleId === 0) {
$subquestion->relevance = $data['relevance'];
}
$subquestion->setScenario('saveall');
if (!$subquestion->save()) {
throw (new LSUserException(500, gT("Could not save subquestion")))
->setDetailedErrorsFromModel($subquestion);
Expand Down Expand Up @@ -2836,8 +2872,7 @@ private function storeSubquestions($question, $subquestionsArray)
private function updateSubquestions($question, $subquestionsArray)
{
$questionOrder = 0;
$subquestionIds = [];
foreach ($subquestionsArray as $subquestionId => $subquestionArray) {
foreach ($subquestionsArray as $subquestionArray) {
foreach ($subquestionArray as $scaleId => $data) {
$subquestion = Question::model()->findByAttributes(
[
Expand Down Expand Up @@ -2870,7 +2905,6 @@ private function updateSubquestions($question, $subquestionsArray)
->setDetailedErrorsFromModel($subquestion);
}
$subquestion->refresh();
$subquestionIds[] = $subquestion->qid;
foreach ($data['subquestionl10n'] as $lang => $questionText) {
$l10n = QuestionL10n::model()->findByAttributes(
[
Expand All @@ -2891,7 +2925,6 @@ private function updateSubquestions($question, $subquestionsArray)
}
}
}
$question->deleteAllSubquestions($subquestionIds);
}

/**
Expand Down
7 changes: 4 additions & 3 deletions application/models/Question.php
Expand Up @@ -143,8 +143,8 @@ public function rules()
{
/* Basic rules */
$aRules = array(
array('title', 'required', 'on' => 'update, insert', 'message' => gT('The question code is mandatory.', 'unescaped')),
array('title', 'length', 'min' => 1, 'max' => 20, 'on' => 'update, insert'),
array('title', 'required', 'on' => 'update, insert, saveall', 'message' => gT('The question code is mandatory.', 'unescaped')),
array('title', 'length', 'min' => 1, 'max' => 20, 'on' => 'update, insert, saveall'),
array('qid,sid,gid,parent_qid', 'numerical', 'integerOnly' => true),
array('qid', 'unique','message' => sprintf(gT("Question id (qid) : '%s' is already in use."), $this->qid)),// Still needed ?
array('other', 'in', 'range' => array('Y', 'N'), 'allowEmpty' => true),
Expand Down Expand Up @@ -182,7 +182,8 @@ public function rules()
':scale_id' => $this->scale_id
)
),
'message' => gT('Subquestion codes must be unique.')
'message' => gT('Subquestion codes must be unique.'),
'except' => 'saveall'
);
/* Disallow other title if question allow other */
$oParentQuestion = Question::model()->findByPk(array("qid" => $this->parent_qid));
Expand Down
Expand Up @@ -22,6 +22,12 @@
'lsdetailurl' => Yii::app()->createUrl('/questionAdministration/getLabelsetDetails'),
'lspickurl' => Yii::app()->createUrl('/questionAdministration/getLabelsetPicker'),
'sCheckLabelURL' => Yii::app()->createUrl('/questionAdministration/checkLabel'),
'lsextraoptionsurl' => Yii::app()->createUrl(
'questionAdministration/ajaxLoadExtraOptions',
[
'questionId' => $qid
]
),
'subquestions' => [
'newansweroption_text' => gT('New subquestion','js'),
'quickaddtitle' => gT('Quick-add subquestion','js'),
Expand Down
85 changes: 60 additions & 25 deletions assets/scripts/admin/questionEditor.js
Expand Up @@ -195,6 +195,29 @@ $(document).on('ready pjax:scriptcomplete', function () {
});
}

function bindSubQuestionEvents() {
$('.btnaddsubquestion').off('click.subquestions').on('click.subquestions', addSubquestionInput);
$('.btndelsubquestion').off('click.subquestions').on('click.subquestions', deleteSubquestionInput);
}

function bindAnswerEvents() {
$('.btnaddanswer').off('click.subquestions').on('click.subquestions', addAnswerOptionInput);
$('.btndelanswer').off('click.subquestions').on('click.subquestions', deleteAnswerOptionInput);
}

function toggleLanguageElements() {
// get selected lang from dropdown
let lang = $('.active .lang-switch-button').data('lang');
//fallback: display main language
if (lang === undefined) {
const languages = languageJson.langs.split(';');
lang = languages[0];
}
const langClass = `.lang-${lang}`;
$('.lang-hide').hide();
$(langClass).show();
}

/**
* @param {number} position
* @param {string} source Either 'subquestions' or 'answeroptions'
Expand Down Expand Up @@ -543,8 +566,7 @@ $(document).on('ready pjax:scriptcomplete', function () {
const target = e.target;
const data = $('#add-subquestion-input-javascript-datas').data();
const rebindClickHandler = () => {
$('.btnaddsubquestion').off('click.subquestions').on('click.subquestions', addSubquestionInput);
$('.btndelsubquestion').off('click.subquestions').on('click.subquestions', deleteSubquestionInput);
bindSubQuestionEvents();
};
addNewInputAux(target, data, rebindClickHandler);
}
Expand Down Expand Up @@ -701,7 +723,7 @@ $(document).on('ready pjax:scriptcomplete', function () {
$bodyItem.append(`<h4>${labelSet.label_name}</h4>`); // jshint ignore: line
$itemList.appendTo($bodyItem);
});

if (isEmpty) {
showLabelSetAlert(languageJson.labelSetEmpty);
} else {
Expand Down Expand Up @@ -740,7 +762,7 @@ $(document).on('ready pjax:scriptcomplete', function () {

/**
* Hides the alert in the label set's modal
*
*
* @return {void}
*/
function hideLabelSetAlert() /*: void */ {
Expand Down Expand Up @@ -1177,15 +1199,9 @@ $(document).on('ready pjax:scriptcomplete', function () {
bindClickIfNotExpanded();

// Unbind and bind events.
$(`.btnaddanswer`).off('click.subquestions');
$(`.btndelanswer`).off('click.subquestions');
$(`.btnaddsubquestion`).off('click.subquestions');
$(`.btndelsubquestion`).off('click.subquestions');
$(`.answer`).off('focus');
$(`.btnaddanswer`).on('click.subquestions', addAnswerOptionInput);
$(`.btndelanswer`).on('click.subquestions', deleteAnswerOptionInput);
$(`.btnaddsubquestion`).on('click.subquestions', addSubquestionInput);
$(`.btndelsubquestion`).on('click.subquestions', deleteSubquestionInput);
bindSubQuestionEvents();
bindAnswerEvents();
},
);
}
Expand Down Expand Up @@ -1252,9 +1268,9 @@ $(document).on('ready pjax:scriptcomplete', function () {
<select name="laname">
<option value=""></option>
</select>
</p>'
</p>'
`;
//
//
child = template.content.firstElementChild;
if (child) {
targetParent.after(child);
Expand Down Expand Up @@ -1556,7 +1572,7 @@ $(document).on('ready pjax:scriptcomplete', function () {
});
return duplicateCodes.length == 0;
}

/**
* Return a function that can be used to check code uniqueness.
* Used by subquestions and answer options.
Expand Down Expand Up @@ -1698,11 +1714,8 @@ $(document).on('ready pjax:scriptcomplete', function () {
const languages = languageJson.langs.split(';');
$('.lang-switch-button[data-lang="' + languages[0] + '"]').trigger('click');

// TODO: Duplication.
$('.btnaddsubquestion').on('click.subquestions', addSubquestionInput);
$('.btndelsubquestion').on('click.subquestions', deleteSubquestionInput);
$('.btnaddanswer').on('click.subquestions', addAnswerOptionInput);
$('.btndelanswer').on('click.subquestions', deleteAnswerOptionInput);
bindSubQuestionEvents();
bindAnswerEvents();
} catch (ex) {
$('#ls-loading').hide();
// TODO: How to show internal errors?
Expand Down Expand Up @@ -1874,6 +1887,29 @@ $(document).on('ready pjax:scriptcomplete', function () {
});
};

const reloadExtraOptions = () => {
// Show loading gif.
$('#ls-loading').show();
// Post complete form to controller.
$.get({
url: languageJson.lsextraoptionsurl,
success: (response /*: string */, textStatus /*: string */) => {
$('#extra-options-container').replaceWith( response );
bindSubQuestionEvents();
bindAnswerEvents();
makeAnswersTableSortable();
toggleLanguageElements();
// Hide loading gif.
$('#ls-loading').hide();
},
error: (data) => {
$('#ls-loading').hide();
alert('Internal error from saveFormWithAjax: no data.responseJSON found');
throw 'abort';
}
});
};

// Helper function after unique check.
const saveFormWithAjax /*: (void) => (void) */ = () => {
const data = {};
Expand Down Expand Up @@ -1911,6 +1947,7 @@ $(document).on('ready pjax:scriptcomplete', function () {

// Update the side-bar.
LS.EventBus.$emit('updateSideBar', {'updateQuestions': true});
reloadExtraOptions();

if (textStatus === 'success') {
// Show confirm message.
Expand Down Expand Up @@ -2032,10 +2069,8 @@ $(document).on('ready pjax:scriptcomplete', function () {

makeAnswersTableSortable();

$('.btnaddsubquestion').off('click.subquestions').on('click.subquestions', addSubquestionInput);
$('.btndelsubquestion').off('click.subquestions').on('click.subquestions', deleteSubquestionInput);
$('.btnaddanswer').off('click.subquestions').on('click.subquestions', addAnswerOptionInput);
$('.btndelanswer').off('click.subquestions').on('click.subquestions', deleteAnswerOptionInput);
bindSubQuestionEvents();
bindAnswerEvents();

$('#labelsetbrowserModal').on('hidden.bs.modal.', labelSetDestruct);

Expand Down Expand Up @@ -2141,7 +2176,7 @@ $(document).on('ready pjax:scriptcomplete', function () {
$('#' + id).width(width).height(height);
$('#' + id).closest('.jquery-ace-wrapper').width(width).height(height);
});

$('#relevance').on('keyup', showConditionsWarning);

$(document).on('focusout', '#subquestions table.subquestions-table:first-of-type td.code-title input.code', syncAnswerSubquestionCode);
Expand Down

0 comments on commit 4ac4251

Please sign in to comment.