[IMP] survey: improve mobile UI

PURPOSE
This commit removes a few unessential content, fixes
misalignments and removes the input autofocus on mobile devices

SPECS

Remove a few unessential content
Some contents take a considerable space on mobile without adding
essential information, requiring the user to scroll to see useful info.
These contents are now removed on mobile devices.

Fix misalignments
Some parts were misaligned on small screens, taking unnecessary space or
cutting parts of sentences. These parts have been adapted to handle
small screens.

Remove action buttons related to live sessions on small screens
Since the presentation mode doesn't look good on mobile devices
(questions have a lot of linebreaks, arrows do not always work),
and since normally no one presents a live session using a mobile
device, the action buttons "Create Live Session", "Open Session Manager"
and "Close Live Session" are now hidden on small screens.

Remove the autofocus on the first input in survey forms on mobile devices
When a new page of a survey is loaded, the first input is autofocused so
that the user can directly complete the survey without manually getting
the focus. This causes inconsistencies and undesired behaviors on mobile
devices. Especially, when the focus is set automatically on Android devices,
the soft keyboard is opened, the viewport is reduced and the view is scrolled
to the input, which involves that the user misses information about the
survey (such as the beginning of the question for long questions), or
section descriptions. The autofocus has therefore been removed for mobile
devices.

Task-2622693

Part-of: odoo/odoo#87990
This commit is contained in:
Adrien Schoffeniels
2022-06-08 11:20:13 +02:00
parent a7438e3278
commit 72c4ffc3ee
10 changed files with 54 additions and 48 deletions
+1
View File
@@ -908,6 +908,7 @@ class Survey(models.Model):
)
return {
'type': 'ir.actions.act_window',
'name': _("Share a Survey"),
'view_mode': 'form',
'res_model': 'survey.invite',
'target': 'new',
+11 -10
View File
@@ -4,6 +4,7 @@ odoo.define('survey.form', function (require) {
var field_utils = require('web.field_utils');
var publicWidget = require('web.public.widget');
var time = require('web.time');
var config = require('web.config');
var core = require('web.core');
var Dialog = require('web.Dialog');
var dom = require('web.dom');
@@ -322,7 +323,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa
*/
_updateEnterButtonText: function (event) {
const $target = event.target;
const isTextbox = event.type == "focusin" && $target.tagName.toLowerCase() === 'textarea';
const isTextbox = event.type === "focusin" && $target.tagName.toLowerCase() === 'textarea';
const text = !isTextbox ? _t('or press Enter') : isMac ? _t("or press ⌘+Enter") : _t("or press CTRL+Enter");
$('#enter-tooltip').text(text);
},
@@ -575,8 +576,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa
this.$('.o_survey_form_content').fadeIn(this.fadeInOutDelay);
$("html, body").animate({ scrollTop: 0 }, this.fadeInOutDelay);
self._focusOnFirstInput();
}
else if (result && result.fields && result.error === 'validation') {
} else if (result && result.fields && result.error === 'validation') {
this.$('.o_survey_form_content').fadeIn(0);
this._showErrors(result.fields);
} else {
@@ -936,7 +936,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa
if (!this._checkIsMasterTab()) {
this.shouldReloadMasterTab = true;
this.masterTabCheckInterval = setInterval(function() {
this.masterTabCheckInterval = setInterval(function () {
if (self._checkIsMasterTab()) {
clearInterval(self.masterTabCheckInterval);
}
@@ -1007,7 +1007,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa
}
$dateGroup.datetimepicker({
format : datetimepickerFormat,
format: datetimepickerFormat,
minDate: minDate,
maxDate: maxDate,
disabledDates: disabledDates,
@@ -1022,7 +1022,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
},
locale : moment.locale(),
locale: moment.locale(),
allowInputToggle: true,
});
$dateGroup.on('error.datetimepicker', function (err) {
@@ -1039,7 +1039,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa
});
},
_formatDateTime: function (datetimeValue, format){
_formatDateTime: function (datetimeValue, format) {
return moment(field_utils.format.datetime(moment(datetimeValue), null, {timezone: true}), format);
},
@@ -1057,14 +1057,15 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa
/**
* Will automatically focus on the first input to allow the user to complete directly the survey,
* without having to manually get the focus (only if the input has the right type - can write something inside -)
* without having to manually get the focus (only if the input has the right type - can write something inside -
* and if the device is not a mobile device to avoid missing information when the soft keyboard is opened)
*/
_focusOnFirstInput: function () {
var $firstTextInput = this.$('.js_question-wrapper').first() // Take first question
.find("input[type='text'],input[type='number'],textarea") // get 'text' inputs
.filter('.form-control') // needed for the auto-resize
.not('.o_survey_comment'); // remove inputs for comments that does not count as answers
if ($firstTextInput.length > 0) {
if ($firstTextInput.length > 0 && !config.device.isMobile) {
$firstTextInput.focus();
}
},
@@ -1085,7 +1086,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa
window.location.reload();
}
return true;
} else if (!$errorModal.modal._isShown){
} else if (!$errorModal.modal._isShown) {
$errorModal.find('.text-danger').text(window.location.hostname);
$errorModal.modal('show');
}
@@ -41,8 +41,8 @@ _::-webkit-full-page-media, _:future, :root .o_survey_wrap {
}
.o_survey_progress_wrapper {
min-width: 11rem;
max-width: 15rem;
min-width: 7rem;
max-width: 11rem;
.o_survey_progress {
height:0.5em;
@@ -294,11 +294,11 @@ _::-webkit-full-page-media, _:future, :root .o_survey_wrap {
cursor: pointer;
&.o_survey_session_navigation_next {
right: 2rem;
right: 1rem;
}
&.o_survey_session_navigation_previous {
left: 2rem;
left: 1rem;
}
}
@@ -48,6 +48,8 @@
.btn.filter-remove-answer {
border-color: #DEE2E6;
background-color: transparent;
white-space: normal;
text-align: left;
i.fa-times {
cursor: pointer;
}
@@ -31,7 +31,7 @@
<field name="question_type" widget="radio" attrs="{'required': [('is_page', '=', False)]}" />
</group>
<group>
<div class="col-lg-6 offset-lg-3 w-100 o_preview_questions">
<div class="col-lg-6 offset-lg-3 d-none d-sm-block w-100 o_preview_questions">
<!-- Multiple choice: only one answer -->
<div attrs="{'invisible': [('question_type', '!=', 'simple_choice')]}" role="img" aria-label="Multiple choice with one answer"
title="Multiple choice with one answer">
+6 -6
View File
@@ -13,11 +13,11 @@
<button name="action_send_survey" string="Share" type="object" class="oe_highlight" attrs="{'invisible': [('active', '=', False)]}"/>
<button name="action_result_survey" string="See results" type="object" class="oe_highlight"
attrs="{'invisible': [('answer_done_count', '&lt;=', 0)]}"/>
<button name="action_start_session" string="Create Live Session" type="object"
<button name="action_start_session" string="Create Live Session" type="object" class="d-none d-sm-block"
attrs="{'invisible': ['|', ('session_state', '!=', False), '|', ('active', '=', False), ('certification', '=', True)]}" />
<button name="action_open_session_manager" string="Open Session Manager" type="object"
<button name="action_open_session_manager" string="Open Session Manager" type="object" class="d-none d-sm-block"
attrs="{'invisible': [('session_state', '=', False)]}" />
<button name="action_end_session" string="Close Live Session" type="object"
<button name="action_end_session" string="Close Live Session" type="object" class="d-none d-sm-block"
attrs="{'invisible': [('session_state', 'not in', ['ready', 'in_progress'])]}" />
<button name="action_test_survey" string="Test" type="object" attrs="{'invisible': ['|', ('active', '=', False), ('question_ids', '=', [])]}"/>
<button name="action_unarchive" string="Reopen" class="btn-primary" type="object" attrs="{'invisible': [('active', '=', True)]}"/>
@@ -228,7 +228,7 @@
</span>
</div>
</div>
<div t-attf-class="col-lg-1 col-sm-4 py-0 my-2 col-#{selection_mode ? '12' : '6'}">
<div t-attf-class="col-lg-1 col-sm-4 d-none d-sm-block py-0 my-2 col-#{selection_mode ? '12' : '6'}">
<span class="font-weight-bold"><field name="question_count"/></span><br t-if="!selection_mode"/>
<span class="text-muted">Questions</span>
</div>
@@ -249,7 +249,7 @@
<span class="text-muted">Registered</span>
</a>
</div>
<div t-if="!selection_mode" class="col-lg-1 col-sm-4 col-6 py-0 my-2">
<div t-if="!selection_mode" class="col-lg-1 col-sm-4 col-6 d-none d-sm-block py-0 my-2">
<a type="object"
name="action_survey_user_input_completed"
class="font-weight-bold">
@@ -271,7 +271,7 @@
<span class="text-muted" t-else="">Certified</span>
</a>
</div>
<div t-if="!selection_mode" class="col-lg-3 col-sm-12 py-0 my-2 pb-lg-3 ml-auto text-lg-right">
<div t-if="!selection_mode" class="col-lg-3 col-sm-12 d-none d-sm-block py-0 my-2 pb-lg-3 ml-auto text-lg-right">
<button name="action_send_survey"
string="Share" type="object"
class="btn btn-secondary border"
+1 -1
View File
@@ -30,7 +30,7 @@
</xpath>
<xpath expr="//footer" position="after">
<div class="py-3 m-0 p-0 text-right">
<div class="o_survey_progress_wrapper d-inline-block pr-5 text-left">
<div class="o_survey_progress_wrapper d-inline-block pr-1 text-left">
<t t-call="survey.survey_progression"
t-if="survey and survey.questions_layout != 'one_page' and answer and answer.state == 'in_progress' and (not question or not question.is_page) and not survey_form_readonly">
<t t-if="survey.questions_layout == 'page_per_section'">
@@ -13,7 +13,7 @@
</template>
<template id="survey_page_statistics_header" name="Survey: result statistics header">
<div class="o_survey_statistics_header py-5 mt32">
<div class="o_survey_statistics_header pt-5 mt32">
<h1>
<span t-field="survey.title"/>
<span style="font-size:1.5em;"
@@ -64,31 +64,33 @@
<t t-set="table_data" t-value="question_data['table_data']"/>
<div class="o_survey_results_question pb-5 border-bottom">
<div class="d-flex align-items-start mb-2">
<div class="mr-auto">
<h5 t-field="question.title" class="mb-3"/>
<div class="row mb-3">
<div class="col-sm-6">
<h5 t-field="question.title" class="mr-3"/>
</div>
<!-- Question info -->
<span class="badge badge-info" t-field='question.question_type'/>
<t t-if="question.question_type == 'matrix'">
<span class="badge badge-info ml-2" t-field='question.matrix_subtype'/>
</t>
<!-- Scoring info -->
<t t-if="question.is_scored_question">
<span class="badge badge-success ml-3"><span t-esc="question_data['right_inputs_count']" class="mr-1"/>Correct</span>
<t t-if="question.question_type in ['simple_choice', 'multiple_choice']">
<span class="badge badge-warning ml-1" t-if="question.question_type == 'multiple_choice'">
<span t-esc="question_data['partial_inputs_count']" class="mr-1"/>Partial
</span>
<div class="col-sm-6">
<span class="badge badge-info mr-1" t-field='question.question_type'/>
<t t-if="question.question_type == 'matrix'">
<span class="badge badge-info ml-1" t-field='question.matrix_subtype'/>
</t>
</t>
<!-- Inputs info -->
<span class="badge badge-info ml-3">
<span t-esc="len(question_data['answer_input_done_ids'])" class="mr-1"/>Answered
</span>
<span class="badge badge-info ml-1">
<span t-esc="len(question_data['answer_input_skipped_ids'])" class="mr-1"/>Skipped
</span>
<!-- Scoring info -->
<t t-if="question.is_scored_question">
<span class="badge badge-success mr-1"><span t-esc="question_data['right_inputs_count']" class="mr-1"/>Correct</span>
<t t-if="question.question_type in ['simple_choice', 'multiple_choice']">
<span class="badge badge-warning mr-1" t-if="question.question_type == 'multiple_choice'">
<span t-esc="question_data['partial_inputs_count']" class="mr-1"/>Partial
</span>
</t>
</t>
<!-- Inputs info -->
<span class="badge badge-info mr-1">
<span t-esc="len(question_data['answer_input_done_ids'])" class="mr-1"/>Answered
</span>
<span class="badge badge-info mr-1">
<span t-esc="len(question_data['answer_input_skipped_ids'])" class="mr-1"/>Skipped
</span>
</div>
</div>
<!-- Question Description -->
@@ -38,7 +38,7 @@
<div class="text-center">
<h1 class="mb-4" t-field="survey.title" />
<h2 class="mb-5 font-weight-normal">
<span>Go to <a t-att-href="survey.session_link" t-esc="survey.session_link" target="_blank" /></span>
<span class="text-break">Go to <a t-att-href="survey.session_link" t-esc="survey.session_link" target="_blank" /></span>
<i class="fa fa-link font-weight-normal ml-3 o_survey_session_copy" />
<input class="o_survey_session_copy_url d-none" type="text" t-att-value="survey.session_link" />
</h2>
@@ -114,7 +114,7 @@
class="font-weight-bold fa fa-chevron-right o_survey_session_navigation o_survey_session_navigation_next p-3" />
<a role="button"
class="font-weight-bold fa fa-chevron-left o_survey_session_navigation o_survey_session_navigation_previous p-3" />
<div class="o_survey_session_results flex-column flex-grow-1">
<div class="o_survey_session_results flex-column flex-grow-1 mx-5">
<div class="row">
<div class="col-lg-12"><h1 t-esc="question.title"></h1></div>
</div>
+1 -1
View File
@@ -25,7 +25,7 @@
attrs="{
'invisible': [('survey_users_login_required', '=', True)],
}"
placeholder="Add a list of email of recipients (will not be converted into contacts). Separated by commas, semicolons or newline..."/>
placeholder="Add a list of email recipients (separated by commas, semicolons or line breaks). They will not be converted into contacts."/>
</group>
<div col="2" class="alert alert-warning" role="alert"
attrs="{'invisible': ['|', ('survey_access_mode', '=', 'public'), ('existing_text', '=', False)]}">