[IMP] survey: improve survey form navigation
This commit slightly changes the way users navigate through the survey form: - The back button was moved to the bottom-right of the screen. - The "Continue" button stays at the center but there is also a "next" button at the bottom-right of the screen (same behavior -> submits the form) - Arrow keys (left + right) navigation is now supported Technical note: As the survey navigation is now outside of the survey content, that was re-rendered on every submit, we have to manually refresh the navigation with a second template render on the controller side. LINKS PR #46768 Task 2208574
This commit is contained in:
@@ -268,10 +268,12 @@ class Survey(http.Controller):
|
||||
if 'previous_page_id' in post:
|
||||
previous_page_or_question_id = int(post['previous_page_id'])
|
||||
new_previous_id = survey_sudo._get_next_page_or_question(answer_sudo, previous_page_or_question_id, go_back=True).id
|
||||
page_or_question = request.env['survey.question'].sudo().browse(previous_page_or_question_id)
|
||||
data.update({
|
||||
page_or_question_key: request.env['survey.question'].sudo().browse(previous_page_or_question_id),
|
||||
page_or_question_key: page_or_question,
|
||||
'previous_page_id': new_previous_id,
|
||||
'has_answered': answer_sudo.user_input_line_ids.filtered(lambda line: line.question_id.id == new_previous_id)
|
||||
'has_answered': answer_sudo.user_input_line_ids.filtered(lambda line: line.question_id.id == new_previous_id),
|
||||
'can_go_back': survey_sudo._can_go_back(answer_sudo, page_or_question),
|
||||
})
|
||||
return data
|
||||
|
||||
@@ -296,7 +298,8 @@ class Survey(http.Controller):
|
||||
|
||||
data.update({
|
||||
page_or_question_key: next_page_or_question,
|
||||
'has_answered': answer_sudo.user_input_line_ids.filtered(lambda line: line.question_id == next_page_or_question)
|
||||
'has_answered': answer_sudo.user_input_line_ids.filtered(lambda line: line.question_id == next_page_or_question),
|
||||
'can_go_back': survey_sudo._can_go_back(answer_sudo, next_page_or_question),
|
||||
})
|
||||
if survey_sudo.questions_layout != 'one_page':
|
||||
data.update({
|
||||
@@ -338,7 +341,8 @@ class Survey(http.Controller):
|
||||
|
||||
return {
|
||||
'survey_content': survey_content,
|
||||
'survey_progress': survey_progress
|
||||
'survey_progress': survey_progress,
|
||||
'survey_navigation': request.env.ref('survey.survey_navigation').render(survey_data),
|
||||
}
|
||||
|
||||
@http.route('/survey/<string:survey_token>/<string:answer_token>', type='http', auth='public', website=True)
|
||||
|
||||
@@ -445,6 +445,30 @@ class Survey(models.Model):
|
||||
|
||||
return questions
|
||||
|
||||
def _can_go_back(self, answer, page_or_question):
|
||||
""" Check if the user can go back to the previous question/page for the currently
|
||||
viewed question/page.
|
||||
Back button needs to be configured on survey and, depending on the layout:
|
||||
- In 'page_per_section', we can go back if we're not on the first page
|
||||
- In 'page_per_question', we can go back if:
|
||||
- It is not a session answer (doesn't make sense to go back in session context)
|
||||
- We are not on the first question
|
||||
- The survey does not have pages OR this is not the first page of the survey
|
||||
(pages are displayed in 'page_per_question' layout when they have a description, see PR#44271)
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
if self.users_can_go_back and answer.state == 'in_progress':
|
||||
if self.questions_layout == 'page_per_section' and page_or_question != self.page_ids[0]:
|
||||
return True
|
||||
elif self.questions_layout == 'page_per_question' and \
|
||||
not answer.is_session_answer and \
|
||||
page_or_question != answer.predefined_question_ids[0] \
|
||||
and (not self.page_ids or page_or_question != self.page_ids[0]):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _has_attempts_left(self, partner, email, invite_token):
|
||||
self.ensure_one()
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
|
||||
self._focusOnFirstInput();
|
||||
// Init event listener
|
||||
if (!self.readonly) {
|
||||
$(document).on('keypress', self._onKeyPress.bind(self));
|
||||
$(document).on('keydown', self._onKeyDown.bind(self));
|
||||
}
|
||||
if (self.options.sessionInProgress &&
|
||||
(self.options.isStartScreen || self.options.hasAnswered || self.options.isPageDescription)) {
|
||||
@@ -56,9 +56,11 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
|
||||
}
|
||||
self._initSessionManagement();
|
||||
|
||||
// Needs global selector as the progress is not within the survey form, but needs to be
|
||||
// updated at the same time
|
||||
// Needs global selector as progress/navigation are not within the survey form, but need
|
||||
//to be updated at the same time
|
||||
self.$surveyProgress = $('.o_survey_progress_wrapper');
|
||||
self.$surveyNavigation = $('.o_survey_navigation_wrapper');
|
||||
self.$surveyNavigation.find('.o_survey_navigation_submit').on('click', self._onSubmit.bind(self));
|
||||
});
|
||||
},
|
||||
|
||||
@@ -69,9 +71,18 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
|
||||
// Handlers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
_onKeyPress: function (event) {
|
||||
// If user is answering a textarea, do not handle keyPress
|
||||
if (this.$("textarea").is(":focus")) {
|
||||
/**
|
||||
* Handle keyboard navigation:
|
||||
* - 'enter' or 'arrow-right' => submit form
|
||||
* - 'arrow-left' => submit form (but go back backwards)
|
||||
* - other alphabetical character ('a', 'b', ...)
|
||||
* Select the related option in the form (if available)
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
_onKeyDown: function (event) {
|
||||
// If user is answering a text input, do not handle keydown
|
||||
if (this.$("textarea").is(":focus") || this.$('input').is(':focus')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,12 +91,17 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
|
||||
var letter = String.fromCharCode(keyCode).toUpperCase();
|
||||
|
||||
// Handle Start / Next / Submit
|
||||
if (keyCode === 13) { // Enter : go Next
|
||||
if (keyCode === 13 || keyCode === 39) { // Enter or arrow-right: go Next
|
||||
event.preventDefault();
|
||||
if (!this.preventEnterSubmit) {
|
||||
var isFinish = this.$('button[value="finish"]').length !== 0;
|
||||
this._submitForm({isFinish: isFinish});
|
||||
}
|
||||
} else if (keyCode === 37) { // arrow-left: previous (if available)
|
||||
// It's easier to actually click on the button (if in the DOM) as it contains necessary
|
||||
// data that are used in the event handler.
|
||||
// Again, global selector necessary since the navigation is outside of the form.
|
||||
$('.o_survey_navigation_submit[value="previous"]').click();
|
||||
} else if (self.options.questionsLayout === 'page_per_question'
|
||||
&& letter.match(/[a-z]/i)) {
|
||||
var $choiceInput = this.$(`input[data-selection-key=${letter}]`);
|
||||
@@ -416,6 +432,11 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
|
||||
this.$surveyProgress.remove();
|
||||
}
|
||||
|
||||
if (result.survey_navigation && this.$surveyNavigation.length !== 0) {
|
||||
this.$surveyNavigation.html(result.survey_navigation);
|
||||
this.$surveyNavigation.find('.o_survey_navigation_submit').on('click', self._onSubmit.bind(self));
|
||||
}
|
||||
|
||||
// Hide timer if end screen (if page_per_question in case of conditional questions)
|
||||
if (self.options.questionsLayout === 'page_per_question' && this.$('.o_survey_finished').length > 0) {
|
||||
options.isFinish = true;
|
||||
|
||||
@@ -27,6 +27,10 @@ nav#oe_main_menu_navbar {
|
||||
}
|
||||
}
|
||||
|
||||
.o_survey_navigation_wrapper .o_survey_navigation_submit {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.o_survey_brand_message {
|
||||
background-color: rgba(255,255,255,0.7);
|
||||
}
|
||||
|
||||
@@ -35,11 +35,13 @@
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
<div class="o_survey_brand_message float-right rounded mr-3">
|
||||
<div class="o_survey_brand_message float-right rounded mr-3 border">
|
||||
<div class="px-2 py-2 d-inline-block" t-call="web.brand_promotion_message">
|
||||
<t t-set="_message"></t>
|
||||
<t t-set="_utm_medium" t-valuef="survey"/>
|
||||
</div>
|
||||
<div class="o_survey_navigation_wrapper d-inline-block" t-call="survey.survey_navigation">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
@@ -183,8 +185,6 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="offset-3 col-6 text-center mt16">
|
||||
<button t-if="survey.users_can_go_back and page != survey.page_ids[0]" type="submit" class="btn btn-secondary"
|
||||
name="button_submit" value="previous" t-att-data-previous-page-id="previous_page_id">Back</button>
|
||||
<button type="submit" t-att-value="'next' if not survey_last else 'finish'" class="btn btn-primary">
|
||||
Continue</button><span class="font-weight-bold text-muted ml-2 d-none d-md-inline"> or press Enter</span>
|
||||
</div>
|
||||
@@ -220,15 +220,6 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="offset-3 col-6 text-center mt16">
|
||||
<!-- Can go back if:
|
||||
- Back button is allowed in configuration
|
||||
- This is not a session answer (doesn't make sense to go back in session context)
|
||||
- This is not the first question
|
||||
- The survey doesn't have pages OR this is not the first page of the survey
|
||||
(pages are displayed in 'page_per_question' layout when they have a description, see PR#44271)
|
||||
-->
|
||||
<button t-if="survey.users_can_go_back and not answer.is_session_answer and question != answer.predefined_question_ids[0] and (not survey.page_ids or question != survey.page_ids[0])"
|
||||
type="submit" class="btn btn-secondary" name="button_submit" value="previous" t-att-data-previous-page-id="previous_page_id">Back</button>
|
||||
<button type="submit" t-att-value="'next' if not survey_last else 'finish'" class="btn btn-primary">
|
||||
<t t-if="answer.is_session_answer">
|
||||
Submit
|
||||
|
||||
@@ -134,5 +134,16 @@
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="survey_navigation" name="Survey: Navigation">
|
||||
<button t-if="can_go_back"
|
||||
type="submit" class="btn p-0 shadow-none o_survey_navigation_submit" name="button_submit" value="previous" t-att-data-previous-page-id="previous_page_id">
|
||||
<i class="border-left fa fa-chevron-left p-2" />
|
||||
</button>
|
||||
<button t-if="survey and survey.questions_layout in ['page_per_question', 'page_per_section'] and answer and answer.state != 'done' and not answer.is_session_answer"
|
||||
type="submit" class="btn p-0 shadow-none o_survey_navigation_submit" t-att-value="'next' if not survey_last else 'finish'">
|
||||
<i class="border-left fa fa-chevron-right p-2" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user