[IMP] survey : redesign frontend layout

In this commit, all the questions type have been redesigned to look more fancy.
(impacted question types : radio and checkboxes, text boxes (textarea and text
inputs, date and datetime inputs and matrix)

For radio and checkboxes, both design have been aligned to work the same way
(except checkboxes can still have more than one selected option).
Selection by key have been applied on those two question types if the number of
option is under 26 (to use all the alphabeat character for selection). This
selection by key is only available on page per question layout.

Tests have been adapted accordingly to the redesign (typically for choice and
matrix inputs)

* Progress Bar

This commit adds a progress bar to the survey to inform the user
where he is in the survey filling process.

There are two progress modes:
    - Number : will display the number of the current page on the total number
    of pages (or questions if layout mode is question_per_page)
    - Percentage : will display the percentage of page or question already done

So this leads to, on last page:
    - in Number mode :
    the progress bar div is 100% filled         3 / 3 pages   [===]
    - in percentage mode :
    the progress bar is aligned to percentage   66% completed [== ]

* Print Widget

A widget is added for survey print mode in order to resize all textarea to fit
their content, instead of showing a scroll bar. This can be usefull if user
wants to print the results. He will get the entire content of the 'textarea
answers' instead of only the two first lines.

* Misc

This commit also :
    - adds background image to survey.
    - redo quizz correction and add some data to illustrate non scored
    questions rendering in print template
    - review breadcrumb style

Note : readonly data option on survey form widget is not set anywhere yet but
the usage is done in prevention of the future work on presenter view for survey
session mode.

Task ID: '2150291'
PR #43237
This commit is contained in:
David Beguin
2020-01-17 16:04:17 +00:00
parent 860768b79b
commit aedeccbeea
21 changed files with 701 additions and 196 deletions
+37 -16
View File
@@ -15,6 +15,8 @@ from odoo.http import request, content_disposition
from odoo.osv import expression
from odoo.tools import format_datetime, format_date
from odoo.addons.web.controllers.main import Binary
_logger = logging.getLogger(__name__)
@@ -283,6 +285,24 @@ class Survey(http.Controller):
return request.render('survey.survey_page_fill',
self._prepare_survey_data(access_data['survey_sudo'], access_data['answer_sudo'], **post))
@http.route('/survey/get_background_image/<string:survey_token>/<string:answer_token>', type='http', auth="public", website=True, sitemap=False)
def survey_get_background(self, survey_token, answer_token):
access_data = self._get_access_data(survey_token, answer_token, ensure_token=True)
if access_data['validity_code'] is not True:
return werkzeug.exceptions.Forbidden()
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
status, headers, image_base64 = request.env['ir.http'].sudo().binary_content(
model='survey.survey', id=survey_sudo.id, field='background_image',
default_mimetype='image/png')
return Binary._content_image_get_response(status, headers, image_base64)
# ----------------------------------------------------------------
# JSON ROUTES to begin / continue survey (ajax navigation) + Tools
# ----------------------------------------------------------------
@http.route('/survey/begin/<string:survey_token>/<string:answer_token>', type='json', auth='public', website=True)
def survey_begin(self, survey_token, answer_token, **post):
""" Route used to start the survey user input and display the first survey page. """
@@ -427,6 +447,7 @@ class Survey(http.Controller):
'review': review,
'survey': survey_sudo,
'answer': answer_sudo,
'scoring_display_correction': survey_sudo.scoring_type == 'scoring_with_answers' and answer_sudo,
'format_datetime': lambda dt: format_datetime(request.env, dt, dt_format=False),
'format_date': lambda date: format_date(request.env, date)
})
@@ -464,23 +485,8 @@ class Survey(http.Controller):
return self._generate_report(succeeded_attempt, download=True)
def _generate_report(self, user_input, download=True):
report = request.env.ref('survey.certification_report').sudo().render_qweb_pdf([user_input.id], data={'report_type': 'pdf'})[0]
report_content_disposition = content_disposition('Certification.pdf')
if not download:
content_split = report_content_disposition.split(';')
content_split[0] = 'inline'
report_content_disposition = ';'.join(content_split)
return request.make_response(report, headers=[
('Content-Type', 'application/pdf'),
('Content-Length', len(report)),
('Content-Disposition', report_content_disposition),
])
# ------------------------------------------------------------
# REPORTING SURVEY ROUTES
# REPORTING SURVEY ROUTES AND TOOLS
# ------------------------------------------------------------
@http.route('/survey/results/<model("survey.survey"):survey>', type='http', auth='user', website=True)
@@ -509,6 +515,21 @@ class Survey(http.Controller):
'search_finished': post.get('finished') == 'true',
})
def _generate_report(self, user_input, download=True):
report = request.env.ref('survey.certification_report').sudo().render_qweb_pdf([user_input.id], data={'report_type': 'pdf'})[0]
report_content_disposition = content_disposition('Certification.pdf')
if not download:
content_split = report_content_disposition.split(';')
content_split[0] = 'inline'
report_content_disposition = ';'.join(content_split)
return request.make_response(report, headers=[
('Content-Type', 'application/pdf'),
('Content-Length', len(report)),
('Content-Disposition', report_content_disposition),
])
def _extract_filters_data(self, survey, post):
search_filters = []
line_filter_domain, line_choices = [], []
@@ -26,6 +26,7 @@
<field name="description" type="html"><p>Test your vendor skills!</p></field>
<field name="certification_give_badge">True</field>
<field name="certification_badge_id" ref="vendor_certification_badge"/>
<field name="background_image" type="base64" file="survey/static/src/img/survey_background_2.jpg"/>
</record>
<!-- Page 1 -->
<record model="survey.question" id="vendor_certification_page_1">
+42
View File
@@ -12,6 +12,7 @@
<field name="questions_layout">page_per_section</field>
<field name="description" type="html">
<p>This small quiz will test your knowledge about our Company. Be prepared !</p></field>
<field name="background_image" type="base64" file="survey/static/src/img/survey_background.jpg"/>
</record>
<!-- Page 1: general informations -->
@@ -279,4 +280,45 @@
<field name="sequence">4</field>
<field name="value">Winter</field>
</record>
<!-- Page 4: Feedback - non scored question -->
<record id="survey_demo_quiz_p4" model="survey.question">
<field name="title">Your feeling</field>
<field name="survey_id" ref="survey_demo_quiz"/>
<field name="sequence">26</field>
<field name="question_type" eval="False"/>
<field name="is_page" eval="True"/>
<field name="description" type="html">
<p>We may be interested by your input.</p></field>
</record>
<record id="survey_demo_quiz_p4_q1" model="survey.question">
<field name="survey_id" ref="survey_demo_quiz"/>
<field name="sequence">27</field>
<field name="title">What do you think about this survey ?</field>
<field name="description" type="html"><span>If you don't like us, please try to be as objective as possible.</span></field>
<field name="question_type">simple_choice</field>
<field name="comments_allowed" eval="True"/>
<field name="comment_count_as_answer" eval="True"/>
<field name="constr_mandatory" eval="False"/>
</record>
<record id="survey_demo_quiz_p4_q1_sug1" model="survey.question.answer">
<field name="question_id" ref="survey_demo_quiz_p4_q1"/>
<field name="sequence">1</field>
<field name="value">Good</field>
</record>
<record id="survey_demo_quiz_p4_q1_sug2" model="survey.question.answer">
<field name="question_id" ref="survey_demo_quiz_p4_q1"/>
<field name="sequence">2</field>
<field name="value">Not Good, Not Bad</field>
</record>
<record id="survey_demo_quiz_p4_q1_sug3" model="survey.question.answer">
<field name="question_id" ref="survey_demo_quiz_p4_q1"/>
<field name="sequence">3</field>
<field name="value">Iznogoud</field>
</record>
<record id="survey_demo_quiz_p4_q1_sug4" model="survey.question.answer">
<field name="question_id" ref="survey_demo_quiz_p4_q1"/>
<field name="sequence">4</field>
<field name="value">I have no idea, I'm a dog!</field>
</record>
</data></odoo>
+5
View File
@@ -30,6 +30,7 @@ class Survey(models.Model):
description_done = fields.Html(
"End Message", translate=True,
help="This message will be displayed when survey is completed")
background_image = fields.Binary("Background Image")
active = fields.Boolean("Active", default=True)
state = fields.Selection(selection=[
('draft', 'Draft'), ('open', 'In Progress'), ('closed', 'Closed')
@@ -49,6 +50,10 @@ class Survey(models.Model):
('random', 'Randomized per section')],
string="Selection", required=True, default='all',
help="If randomized is selected, add the number of random questions next to the section.")
progression_mode = fields.Selection([
('percent', 'Percentage'),
('number', 'Number')], string='Progression Mode', default='percent',
help="If Number is selected, it will display the number of questions answered on the total number of question to answer.")
# attendees
user_input_ids = fields.One2many('survey.user_input', 'survey_id', string='User responses', readonly=True, groups='survey.group_survey_user')
# security / access
Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

+110 -5
View File
@@ -6,11 +6,13 @@ var publicWidget = require('web.public.widget');
var time = require('web.time');
var core = require('web.core');
var _t = core._t;
var dom = require('web.dom');
publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
selector: '.o_survey_form',
events: {
'change .o_survey_form_choice_item': '_onChangeChoiceItem',
'click .o_survey_matrix_btn': '_onMatrixBtnClick',
'click button[type="submit"]': '_onSubmit',
},
custom_events: {
@@ -29,6 +31,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
this.fadeInOutDelay = 400;
return this._super.apply(this, arguments).then(function () {
self.options = self.$target.find('form').data();
// Init fields
if (!self.options.isStartScreen) {
self._initTimer();
self._initBreadcrumb();
@@ -36,6 +39,13 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
self.$('div.o_survey_form_date').each(function () {
self._initDateTimePicker($(this));
});
self._initChoiceItems();
self._initTextArea();
self._focusOnFirstInput();
// Init event listener
if (!self.options.readonly) {
$(document).on('keypress', self._onKeyPress.bind(self));
}
});
},
@@ -46,6 +56,35 @@ 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")) {
return;
}
var self = this;
var keyCode = event.keyCode;
var letter = String.fromCharCode(keyCode).toUpperCase();
// Handle Start / Next / Submit
if (keyCode === 13) { // Enter : go Next
event.preventDefault();
this._submitForm({});
} else if (self.options.questionsLayout === 'page_per_question'
&& letter.match(/[a-z]/i)) {
var $choiceInput = this.$(`input[data-selection-key=${letter}]`);
if ($choiceInput.length === 1) {
if ($choiceInput.attr('type') === 'radio') {
$choiceInput.prop("checked", true).trigger('change');
} else {
$choiceInput.prop("checked", !$choiceInput.prop("checked")).trigger('change');
}
// Avoid selection key to be typed into the textbox if 'other' is selected by key
event.preventDefault();
}
}
},
/**
* Checks, if the 'other' choice is checked. Applies only if the comment count as answer.
* If not checked : Clear the comment textarea and disable it
@@ -55,7 +94,8 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
* @param {Event} event
*/
_onChangeChoiceItem: function (event) {
var $choiceItemGroup = $(event.currentTarget).parents('.o_survey_form_choice');
var $target = $(event.currentTarget);
var $choiceItemGroup = $target.closest('.o_survey_form_choice');
var $otherItem = $choiceItemGroup.find('.o_survey_js_form_other_comment');
var $commentInput = $choiceItemGroup.find('textarea[type="text"]');
@@ -68,6 +108,36 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
$commentInput.val('');
$commentInput.enable(false);
}
var $matrixBtn = $target.closest('.o_survey_matrix_btn');
if ($target.attr('type') === 'radio') {
if ($matrixBtn.length > 0) {
$matrixBtn.closest('tr').find('td').removeClass('o_survey_selected');
$matrixBtn.addClass('o_survey_selected');
} else {
$choiceItemGroup.find('label').removeClass('o_survey_selected');
$target.closest('label').addClass('o_survey_selected');
}
} else { // $target.attr('type') === 'checkbox'
if ($matrixBtn.length > 0) {
$matrixBtn.toggleClass('o_survey_selected', !$matrixBtn.hasClass('o_survey_selected'));
} else {
var $label = $target.closest('label');
$label.toggleClass('o_survey_selected', !$label.hasClass('o_survey_selected'));
}
}
},
_onMatrixBtnClick: function (event) {
if (!this.options.readonly) {
var $target = $(event.currentTarget);
var $input = $target.find('input');
if ($input.attr('type') === 'radio') {
$input.prop("checked", true).trigger('change');
} else {
$input.prop("checked", !$input.prop("checked")).trigger('change');
}
}
},
_onSubmit: function (event) {
@@ -177,8 +247,11 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
} else {
this._updateBreadcrumb();
}
self._initChoiceItems();
self._initTextArea();
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') {
this.$('.o_survey_form_content').fadeIn(0);
@@ -321,7 +394,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
*/
_prepareSubmitValues: function (formData, params) {
var self = this;
formData.forEach(function (value, key){
formData.forEach(function (value, key) {
switch (key) {
case 'csrf_token':
case 'token':
@@ -463,6 +536,25 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
// INIT FIELDS TOOLS
// -------------------------------------------------------------------------
/**
* Will allow the textarea to resize on carriage return instead of showing scrollbar.
*/
_initTextArea: function () {
this.$('textarea').each(function () {
dom.autoresize($(this));
});
},
_initChoiceItems: function () {
this.$("input[type='radio'],input[type='checkbox']").each(function () {
var matrixBtn = $(this).parents('.o_survey_matrix_btn');
if ($(this).prop("checked")) {
var $target = matrixBtn.length > 0 ? matrixBtn : $(this).closest('label');
$target.addClass('o_survey_selected');
}
});
},
/**
* Will initialize the breadcrumb widget that handles navigation to a previously filled in page.
*
@@ -573,6 +665,17 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
}
},
/**
* 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 -)
*/
_focusOnFirstInput: function () {
var $inputs = this.$("input[type='text'],input[type='number'],textarea").not('.o_survey_comment');
if ($inputs.length > 0) {
$inputs.first().focus();
}
},
// ERRORS TOOLS
// -------------------------------------------------------------------------
@@ -591,10 +694,12 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
var scrollLocation = $target.offset().top;
var navbarHeight = $('.o_main_navbar').height();
if (navbarHeight) {
scrollLocation -= navbarHeight;
// In overflow auto, scrollLocation of target can be negative if target is out of screen (up side)
scrollLocation = scrollLocation >= 0 ? scrollLocation - navbarHeight : scrollLocation + navbarHeight;
}
$('html, body').animate({
scrollTop: scrollLocation
var scrollinside = $("#wrapwrap").scrollTop();
$('#wrapwrap').animate({
scrollTop: scrollinside + scrollLocation
}, 500);
},
@@ -0,0 +1,31 @@
odoo.define('survey.print', function (require) {
'use strict';
var publicWidget = require('web.public.widget');
var dom = require('web.dom');
publicWidget.registry.SurveyPrintWidget = publicWidget.Widget.extend({
selector: '.o_survey_print',
//--------------------------------------------------------------------------
// Widget
//--------------------------------------------------------------------------
/**
* @override
*/
start: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
// Will allow the textarea to resize if any carriage return instead of showing scrollbar.
self.$('textarea').each(function () {
dom.autoresize($(this));
});
});
},
});
return publicWidget.registry.SurveyPrintWidget;
});
@@ -0,0 +1,193 @@
.o_survey_wrap {
min-height: 100%;
}
/**********************************************************
Common Style
**********************************************************/
.o_survey_form, .o_survey_print {
.o_survey_question_error {
height: 0px;
transition: height .5s ease;
line-height: 4rem;
&.slide_in {
height: 4rem;
}
}
.o_survey_question_text_box,
.o_survey_question_date,
.o_survey_question_datetime,
.o_survey_question_numerical_box {
border: 0px;
border-bottom: 2px solid $o-brand-primary;
&:disabled {
color: black !important;
border-color: $gray-600;
border-bottom: 2px solid $gray-600;
}
&:focus {
border-color: $o-brand-primary;
}
}
.o_survey_form_date .input-group-append {
right: 0;
.input-group-text i {
font-size: large;
}
}
.o_survey_choice_btn {
transition: background-color 0.3s ease;
flex: 1 0 300px;
span {
line-height: 25px;
}
i {
top: 0px;
font-size: large;
&.fa-check-circle,&.fa-check-square {
display: none;
}
}
&.o_survey_selected i {
display: none;
&.fa-check-circle,&.fa-check-square {
display: inline;
}
}
}
.o_survey_question_matrix {
td {
min-width: 100px;
i {
font-size: 22px;
display: none;
&.o_survey_matrix_empty_checkbox {
display: inline;
}
}
.o_survey_choice_key > span > span {
top: 0px;
}
&.o_survey_selected {
i {
display: inline;
&.o_survey_matrix_empty_checkbox {
display: none;
}
}
}
}
}
}
/**********************************************************
Form Specific Style
**********************************************************/
.o_survey_form {
min-height: 100%;
.o_survey_choice_btn {
background-color: rgba($o-brand-primary, 0.5);
border: 2px solid $o-brand-primary;
cursor: pointer;
&.o_survey_selected {
background-color: $o-brand-primary;
}
&:hover {
background-color: rgba($o-brand-primary, 0.8);
.o_survey_choice_key span.o_survey_key {
opacity: 1;
}
}
}
.o_survey_choice_key {
width: 25px;
height: 25px;
border: 2px solid $o-brand-primary;
span {
font-size: smaller;
top: -2px;
&.o_survey_key {
width: inherit;
right: 10px;
border: 2px solid $o-brand-primary;
border-right: 0px;
height: 25px;
opacity: 0;
transition: opacity 0.4s ease;
white-space: nowrap;
}
}
}
.o_survey_question_matrix {
th {
background-color: $o-brand-primary;
}
td {
background-color: rgba($o-brand-primary, 0.2);
&:hover {
background-color: rgba($o-brand-primary, 0.5);
cursor: pointer;
.o_survey_choice_key span.o_survey_key {
opacity: 1;
}
}
}
}
.o_survey_progress {
height:0.5em;
}
}
/**********************************************************
Print Specific Style
**********************************************************/
.o_survey_print {
.o_survey_choice_btn {
background-color: $gray-500;
border-color: transparent;
cursor: default;
&.bg-success, &.bg-danger {
opacity: 0.6;
}
&.o_survey_selected {
background-color: $gray-600;
opacity: 1;
}
i.fa-square, i.fa-circle {
display: none;
}
}
.o_survey_question_matrix {
th {
/* important needed to force override bg-primary set on th in the template */
background-color: $gray-600 !important;
}
td {
background-color: $gray-200;
&:hover {
cursor: default;
}
}
i.fa-check-square, i.fa-check-circle, i.o_survey_matrix_empty_checkbox {
color: $gray-600;
}
}
}
@@ -1,28 +0,0 @@
.o_survey_header {
padding-top: 15px;
padding-bottom: 25px;
}
.o_survey_title {
padding-bottom: 15px;
}
.js_question-wrapper {
padding-bottom: 25px;
}
.o_survey_breadcrumb_container {
min-height: 3rem;
}
.o_survey_question_error {
overflow: hidden;
height: 0px;
border: 0px;
padding: 0rem 0.75rem;
transition: height .5s ease;
line-height: 4rem;
&.slide_in {
height: 4rem;
}
}
@@ -2,10 +2,11 @@
<templates id="template" xml:space="preserve">
<t t-name="survey.survey_breadcrumb_template">
<ol class="breadcrumb justify-content-end">
<ol class="breadcrumb justify-content-end bg-transparent">
<t t-set="canGoBack" t-value="widget.canGoBack"/>
<t t-foreach="widget.pages" t-as="page">
<li t-att-class="'breadcrumb-item' + (page.id === widget.currentPageId ? ' active' : '')"
<t t-set="isActivePage" t-value="page.id === widget.currentPageId"/>
<li t-att-class="'breadcrumb-item' + (isActivePage ? ' active font-weight-bold' : '')"
t-att-data-page-id="page.id"
t-att-data-page-title="page.title">
<t t-if="widget.currentPageId === page.id">
@@ -14,12 +15,13 @@
<t t-set="canGoBack" t-value="false" />
</t>
<t t-if="canGoBack">
<a href="#">
<a class="text-primary" href="#">
<span t-esc="page.title" />
</a>
</t>
<t t-else="">
<span t-esc="page.title" />
<span t-att-class="(isActivePage ? 'text-black' : 'text-muted')"
t-esc="page.title" />
</t>
</li>
</t>
@@ -24,43 +24,43 @@ var failSteps = [{ // Page-1
trigger: 'button.btn.btn-primary.btn-lg:contains("Start Certification")',
}, { // Question: Do we sell Acoustic Bloc Screens?
content: "Selecting answer 'No'",
trigger: 'div.js_question-wrapper:contains("Do we sell Acoustic Bloc Screens") label:contains("No") input',
trigger: 'div.js_question-wrapper:contains("Do we sell Acoustic Bloc Screens") label:contains("No")',
}, { // Question: Select all the existing products
content: "Ticking answer 'Fanta'",
trigger: 'div.js_question-wrapper:contains("Select all the existing products") label:contains("Fanta") input'
trigger: 'div.js_question-wrapper:contains("Select all the existing products") label:contains("Fanta")'
}, {
content: "Ticking answer 'Drawer'",
trigger: 'div.js_question-wrapper:contains("Select all the existing products") label:contains("Drawer") input'
trigger: 'div.js_question-wrapper:contains("Select all the existing products") label:contains("Drawer")'
}, {
content: "Ticking answer 'Conference chair'",
trigger: 'div.js_question-wrapper:contains("Select all the existing products") label:contains("Conference chair") input'
trigger: 'div.js_question-wrapper:contains("Select all the existing products") label:contains("Conference chair")'
}, { // Question: Select all the available customizations for our Customizable Desk
content: "Ticking answer 'Color'",
trigger: 'div.js_question-wrapper:contains("Select all the available customizations for our Customizable Desk") label:contains("Color") input'
trigger: 'div.js_question-wrapper:contains("Select all the available customizations for our Customizable Desk") label:contains("Color")'
}, {
content: "Ticking answer 'Height'",
trigger: 'div.js_question-wrapper:contains("Select all the available customizations for our Customizable Desk") label:contains("Height") input'
trigger: 'div.js_question-wrapper:contains("Select all the available customizations for our Customizable Desk") label:contains("Height")'
}, { // Question: How many versions of the Corner Desk do we have?
content: "Selecting answer '2'",
trigger: 'div.js_question-wrapper:contains("How many versions of the Corner Desk do we have") label:contains("2") input',
trigger: 'div.js_question-wrapper:contains("How many versions of the Corner Desk do we have") label:contains("2")',
}, { // Question: Do you think we have missing products in our catalog? (not rated)
content: "Missing products",
trigger: 'div.js_question-wrapper:contains("Do you think we have missing products in our catalog") textarea',
run: "text I don't know products enough to be able to answer that",
}, { // Page-2 Question: How much do we sell our Cable Management Box?
content: "Selecting answer '80$'",
trigger: 'div.js_question-wrapper:contains("How much do we sell our Cable Management Box") label:contains("80$") input',
trigger: 'div.js_question-wrapper:contains("How much do we sell our Cable Management Box") label:contains("80$")',
}, { // Question: Select all the the products that sell for 100$ or more
content: "Ticking answer 'Corner Desk Right Sit'",
trigger: 'div.js_question-wrapper:contains("Select all the the products that sell for 100$ or more") label:contains("Corner Desk Right Sit") input'
trigger: 'div.js_question-wrapper:contains("Select all the the products that sell for 100$ or more") label:contains("Corner Desk Right Sit")'
}, {
content: "Ticking answer 'Desk Combination'",
trigger: 'div.js_question-wrapper:contains("Select all the the products that sell for 100$ or more") label:contains("Desk Combination") input'
trigger: 'div.js_question-wrapper:contains("Select all the the products that sell for 100$ or more") label:contains("Desk Combination")'
}, {
content: "Ticking answer 'Office Chair Black'",
trigger: 'div.js_question-wrapper:contains("Select all the the products that sell for 100$ or more") label:contains("Office Chair Black") input'
trigger: 'div.js_question-wrapper:contains("Select all the the products that sell for 100$ or more") label:contains("Office Chair Black")'
}, { // Question: What do you think about our prices (not rated)?
trigger: 'div.js_question-wrapper:contains("What do you think about our prices") label:contains("Correctly priced") input',
trigger: 'div.js_question-wrapper:contains("What do you think about our prices") label:contains("Correctly priced")',
}, {
content: "Finish Survey",
trigger: 'button[type="submit"]',
@@ -23,44 +23,44 @@ tour.register('test_certification_success', {
trigger: 'button.btn.btn-primary.btn-lg:contains("Start Certification")',
}, { // Question: Do we sell Acoustic Bloc Screens?
content: "Selecting answer 'Yes'",
trigger: 'div.js_question-wrapper:contains("Do we sell Acoustic Bloc Screens") label:contains("Yes") input',
trigger: 'div.js_question-wrapper:contains("Do we sell Acoustic Bloc Screens") label:contains("Yes")',
}, { // Question: Select all the existing products
content: "Ticking answer 'Chair floor protection'",
trigger: 'div.js_question-wrapper:contains("Select all the existing products") label:contains("Chair floor protection") input'
trigger: 'div.js_question-wrapper:contains("Select all the existing products") label:contains("Chair floor protection")'
}, {
content: "Ticking answer 'Drawer'",
trigger: 'div.js_question-wrapper:contains("Select all the existing products") label:contains("Drawer") input'
trigger: 'div.js_question-wrapper:contains("Select all the existing products") label:contains("Drawer")'
}, {
content: "Ticking answer 'Conference chair'",
trigger: 'div.js_question-wrapper:contains("Select all the existing products") label:contains("Conference chair") input'
trigger: 'div.js_question-wrapper:contains("Select all the existing products") label:contains("Conference chair")'
}, { // Question: Select all the available customizations for our Customizable Desk
content: "Ticking answer 'Color'",
trigger: 'div.js_question-wrapper:contains("Select all the available customizations for our Customizable Desk") label:contains("Color") input'
trigger: 'div.js_question-wrapper:contains("Select all the available customizations for our Customizable Desk") label:contains("Color")'
}, {
content: "Ticking answer 'Legs'",
trigger: 'div.js_question-wrapper:contains("Select all the available customizations for our Customizable Desk") label:contains("Legs") input'
trigger: 'div.js_question-wrapper:contains("Select all the available customizations for our Customizable Desk") label:contains("Legs")'
}, { // Question: How many versions of the Corner Desk do we have?
content: "Selecting answer '2'",
trigger: 'div.js_question-wrapper:contains("How many versions of the Corner Desk do we have") label:contains("2") input',
trigger: 'div.js_question-wrapper:contains("How many versions of the Corner Desk do we have") label:contains("2")',
}, { // Question: Do you think we have missing products in our catalog? (not rated)
content: "Missing products",
trigger: 'div.js_question-wrapper:contains("Do you think we have missing products in our catalog") textarea',
run: "text I think we should make more versions of the customizable desk, it's such an amazing product!",
}, { // Page-2 Question: How much do we sell our Cable Management Box?
content: "Selecting answer '80$' (wrong one)",
trigger: 'div.js_question-wrapper:contains("How much do we sell our Cable Management Box") label:contains("80$") input',
trigger: 'div.js_question-wrapper:contains("How much do we sell our Cable Management Box") label:contains("80$")',
}, { // Question: Select all the the products that sell for 100$ or more
content: "Ticking answer 'Corner Desk Right Sit'",
trigger: 'div.js_question-wrapper:contains("Select all the the products that sell for 100$ or more") label:contains("Corner Desk Right Sit") input'
trigger: 'div.js_question-wrapper:contains("Select all the the products that sell for 100$ or more") label:contains("Corner Desk Right Sit")'
}, {
content: "Ticking answer 'Desk Combination'",
trigger: 'div.js_question-wrapper:contains("Select all the the products that sell for 100$ or more") label:contains("Desk Combination") input'
trigger: 'div.js_question-wrapper:contains("Select all the the products that sell for 100$ or more") label:contains("Desk Combination")'
}, {
content: "Ticking answer 'Large Desk'",
trigger: 'div.js_question-wrapper:contains("Select all the the products that sell for 100$ or more") label:contains("Large Desk") input'
trigger: 'div.js_question-wrapper:contains("Select all the the products that sell for 100$ or more") label:contains("Large Desk")'
}, { // Question: What do you think about our prices (not rated)?
content: "Selecting answer 'Underpriced'",
trigger: 'div.js_question-wrapper:contains("What do you think about our prices") label:contains("Underpriced") input',
trigger: 'div.js_question-wrapper:contains("What do you think about our prices") label:contains("Underpriced")',
}, {
content: "Finish Survey",
trigger: 'button[type="submit"]',
+8 -8
View File
@@ -21,7 +21,7 @@ tour.register('test_survey', {
run: 'text 05/05/1980',
}, {
content: 'Answer How frequently do you buy products online',
trigger: 'div.js_question-wrapper:contains("How frequently do you buy products online") label:contains("Once a month") input',
trigger: 'div.js_question-wrapper:contains("How frequently do you buy products online") label:contains("Once a month")',
}, {
content: 'Answer How many times did you order products on our website',
trigger: 'div.js_question-wrapper:contains("How many times did you order products on our website") input',
@@ -33,25 +33,25 @@ tour.register('test_survey', {
// Page-2
{
content: 'Answer Which of the following words would you use to describe our products (High Quality)',
trigger: 'div.js_question-wrapper:contains("Which of the following words would you use to describe our products") label:contains("High quality") input',
trigger: 'div.js_question-wrapper:contains("Which of the following words would you use to describe our products") label:contains("High quality")',
}, {
content: 'Answer Which of the following words would you use to describe our products (Good value for money)',
trigger: 'div.js_question-wrapper:contains("Which of the following words would you use to describe our products") label:contains("Good value for money") input',
trigger: 'div.js_question-wrapper:contains("Which of the following words would you use to describe our products") label:contains("Good value for money")',
}, {
content: 'Answer What do your think about our new eCommerce (The new layout and design is fresh and up-to-date)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("The new layout and design is fresh and up-to-date") input:first',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("The new layout and design is fresh and up-to-date") td:first',
}, {
content: 'Answer What do your think about our new eCommerce (It is easy to find the product that I want)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("It is easy to find the product that I want") input:eq(2)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("It is easy to find the product that I want") td:eq(2)',
}, {
content: 'Answer What do your think about our new eCommerce (The tool to compare the products is useful to make a choice)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("The tool to compare the products is useful to make a choice") input:eq(3)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("The tool to compare the products is useful to make a choice") td:eq(3)',
}, {
content: 'Answer What do your think about our new eCommerce (The checkout process is clear and secure)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("The checkout process is clear and secure") input:eq(2)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("The checkout process is clear and secure") td:eq(2)',
}, {
content: 'Answer What do your think about our new eCommerce (I have added products to my wishlist)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("I have added products to my wishlist") input:last',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("I have added products to my wishlist") td:last',
}, {
content: 'Answer Do you have any other comments, questions, or concerns',
trigger: 'div.js_question-wrapper:contains("Do you have any other comments, questions, or concerns") textarea',
@@ -16,7 +16,7 @@ tour.register('test_survey_prefill', {
trigger: 'div.js_question-wrapper:contains("When is your date of birth ?") input',
run: 'text 05/05/1980',
}, { // Question: How frequently do you buy products online ?
trigger: 'div.js_question-wrapper:contains("How frequently do you buy products online ?") label:contains("Once a week") input',
trigger: 'div.js_question-wrapper:contains("How frequently do you buy products online ?") label:contains("Once a week")',
}, { // Question: How many times did you order products on our website ?
trigger: 'div.js_question-wrapper:contains("How many times did you order products on our website ?") input',
run: 'text 42',
@@ -27,25 +27,25 @@ tour.register('test_survey_prefill', {
// Page-2
{ // Question: Which of the following words would you use to describe our products ?
content: 'Answer Which of the following words would you use to describe our products (High Quality)',
trigger: 'div.js_question-wrapper:contains("Which of the following words would you use to describe our products") label:contains("High quality") input',
trigger: 'div.js_question-wrapper:contains("Which of the following words would you use to describe our products") label:contains("High quality")',
}, {
content: 'Answer Which of the following words would you use to describe our products (Good value for money)',
trigger: 'div.js_question-wrapper:contains("Which of the following words would you use to describe our products") label:contains("Good value for money") input',
trigger: 'div.js_question-wrapper:contains("Which of the following words would you use to describe our products") label:contains("Good value for money")',
}, {
content: 'Answer What do your think about our new eCommerce (The new layout and design is fresh and up-to-date)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("The new layout and design is fresh and up-to-date") input:first',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("The new layout and design is fresh and up-to-date") td:first',
}, {
content: 'Answer What do your think about our new eCommerce (It is easy to find the product that I want)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("It is easy to find the product that I want") input:eq(2)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("It is easy to find the product that I want") td:eq(2)',
}, {
content: 'Answer What do your think about our new eCommerce (The tool to compare the products is useful to make a choice)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("The tool to compare the products is useful to make a choice") input:eq(3)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("The tool to compare the products is useful to make a choice") td:eq(3)',
}, {
content: 'Answer What do your think about our new eCommerce (The checkout process is clear and secure)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("The checkout process is clear and secure") input:eq(2)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("The checkout process is clear and secure") td:eq(2)',
}, {
content: 'Answer What do your think about our new eCommerce (I have added products to my wishlist)',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("I have added products to my wishlist") input:last',
trigger: 'div.js_question-wrapper:contains("What do your think about our new eCommerce") tr:contains("I have added products to my wishlist") td:last',
}, {
content: 'Answer Do you have any other comments, questions, or concerns',
trigger: 'div.js_question-wrapper:contains("Do you have any other comments, questions, or concerns") textarea',
+3 -1
View File
@@ -10,11 +10,13 @@
<script type="text/javascript" src="/survey/static/src/js/survey_timer.js" />
<script type="text/javascript" src="/survey/static/src/js/survey_breadcrumb.js" />
<script type="text/javascript" src="/survey/static/src/js/survey_form.js" />
<script type="text/javascript" src="/survey/static/src/js/survey_print.js" />
<script type="text/javascript" src="/survey/static/src/js/survey_result.js" />
<t t-call="web._assets_helpers"/>
<link href="/survey/static/src/css/survey_print.css" rel="stylesheet" type="text/css"/>
<link href="/survey/static/src/css/survey_result.css" rel="stylesheet" type="text/css"></link>
<link rel="stylesheet" type="text/scss" href="/survey/static/src/scss/survey_templates.scss"/>
<link rel="stylesheet" type="text/scss" href="/survey/static/src/scss/survey_form.scss"/>
</template>
<template id="survey_report_assets_pdf" inherit_id="web.report_assets_pdf">
@@ -42,6 +42,7 @@
</button>
</div>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<field name="background_image" widget="image" class="oe_avatar"/>
<div class="oe_title" style="width: 100%;">
<label for="title" class="oe_edit_only"/>
<h1><field name="title" placeholder="e.g. Satisfaction Survey"/></h1>
@@ -75,6 +76,7 @@
<group name="options">
<group string="Questions" name="questions">
<field name="questions_layout" widget="radio" />
<field name="progression_mode" widget="radio" />
<label for="is_time_limited" string="Time Limit"/>
<div>
<field name="is_time_limited" nolabel="1"/>
+218 -89
View File
@@ -3,6 +3,9 @@
<data>
<!-- Main survey layout -->
<template id="survey.layout" name="Survey Layout" inherit_id="web.frontend_layout" primary="True">
<xpath expr="//div[@id='wrapwrap']" position="attributes">
<attribute name="t-att-style" add="('height: 100%; overflow: auto; background: url(' + '/survey/get_background_image/%s/%s' % (survey.access_token, answer.access_token) + ') no-repeat fixed center; box-shadow: inset 0 0 0 10000px rgba(255,255,255,.7); background-size: cover;') if survey and survey.background_image and answer else 'height: 100%; overflow: auto;'"/>
</xpath>
<xpath expr="//head/t[@t-call-assets][last()]" position="after">
<t t-call-assets="survey.survey_assets" lazy_load="True"/>
</xpath>
@@ -17,8 +20,8 @@
<template id="survey_page_fill" name="Survey: main page (take survey)">
<t t-call="survey.layout">
<div class="wrap">
<div class="container o_survey_form">
<div class="wrap o_survey_wrap mt16 pb16 d-flex">
<div class="container o_survey_form d-flex flex-column">
<t t-if="answer.test_entry" t-call="survey.survey_button_form_view" />
<t t-call="survey.survey_fill_header" />
@@ -29,7 +32,7 @@
</template>
<template id="survey_fill_header" name="Survey: main page header">
<div class="o_page_header o_survey_header">
<div class="o_survey_nav pt16 pb24">
<div class="container m-0 p-0">
<div class="row">
<div class="col-lg-8"><h1 t-esc="survey.title"></h1></div>
@@ -38,7 +41,7 @@
</div>
</div>
</div>
<div t-att-class="'o_survey_breadcrumb_container mt8 bg-200' + (' d-none ' if answer.state != 'in_progress' else '')"
<div t-att-class="'o_survey_breadcrumb_container mt8' + (' d-none ' if answer.state != 'in_progress' else '')"
t-if="survey.questions_layout == 'page_per_section' and answer.state != 'done'"
t-att-data-can-go-back="survey.users_can_go_back"
t-att-data-pages="json.dumps(breadcrumb_pages)" />
@@ -48,19 +51,21 @@
<template id="survey_fill_form" name="Survey: main page content">
<t t-set="survey_form_readonly" t-value="false"/>
<form role="form" method="post" t-att-name="survey.id"
class="d-flex flex-grow-1 align-items-center"
t-att-data-answer-token="answer.access_token"
t-att-data-survey-token="survey.access_token"
t-att-data-timer="answer.start_datetime.isoformat() if survey.is_time_limited
and answer.start_datetime and answer.state != 'done' else False"
t-att-data-time-limit-minutes="survey.time_limit"
t-att-data-is-start-screen="answer.state == 'new'">
t-att-data-is-start-screen="answer.state == 'new'"
t-att-data-questions-layout="survey.questions_layout">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input type="hidden" name="token" t-att-value="answer.access_token" />
<div class="o_survey_error alert alert-danger d-none" role="alert">
<p>There was an error during the validation of the survey.</p>
</div>
<div class="o_survey_form_content">
<div class="o_survey_form_content w-100">
<t t-if="answer.state == 'new'" t-call="survey.survey_fill_form_start"/>
<t t-elif="answer.state == 'in_progress'" t-call="survey.survey_fill_form_in_progress" />
<t t-else="" t-call="survey.survey_fill_form_done"/>
@@ -83,7 +88,7 @@
</template>
<template id="survey_fill_form_start" name="Survey: start form content">
<div class="wrap o_survey_start">
<div class="wrap o_survey_start pb256">
<div class='mt32 mb32'>
<div t-field='survey.description' class="oe_no_empty"/>
<div t-if="survey.is_time_limited">
@@ -99,7 +104,7 @@
<t t-else="">
Start Survey
</t>
</button>
</button><span class="font-weight-bold ml-2 text-primary d-none d-md-inline">or press Enter</span>
</div>
</div>
</template>
@@ -107,17 +112,18 @@
<template id="survey_fill_form_in_progress" name="Survey: form with questions">
<t t-if="survey.questions_layout == 'one_page'">
<t t-foreach='survey.question_and_page_ids' t-as='question'>
<h2 t-if="question.is_page" t-field='question.title' class="o_survey_title" />
<h2 t-if="question.is_page" t-field='question.title' class="o_survey_title pb16" />
<t t-if="not question.is_page and question in answer.predefined_question_ids" t-call="survey.question_container"/>
</t>
<div class="text-center mt16 mb256">
<button type="submit" value="finish" class="btn btn-primary">Submit Survey</button>
<button type="submit" value="finish" class="btn btn-primary">
Submit</button><span class="font-weight-bold ml-2 text-primary d-none d-md-inline">or press Enter</span>
</div>
</t>
<t t-if="survey.questions_layout == 'page_per_section'">
<h2 t-field='page.title' class="o_survey_title" />
<h2 t-field='page.title' class="o_survey_title pb16" />
<div t-field='page.description' class="oe_no_empty"/>
<input type="hidden" name="page_id" t-att-value="page.id" />
@@ -125,12 +131,19 @@
<t t-if="question in answer.predefined_question_ids" t-call="survey.question_container"/>
</t>
<div class="text-center mt16 mb256">
<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">Previous page</button>
<button type="submit" t-att-value="'next' if not last else 'finish'" class="btn btn-primary">
<t t-esc="'Next Page' if not last else 'Submit Survey'"/>
</button>
<div class="row">
<div class="offset-3 col-6 text-center mt16 mb128">
<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 last else 'finish'" class="btn btn-primary">
Continue</button><span class="font-weight-bold ml-2 text-primary d-none d-md-inline"> or press Enter</span>
</div>
<div class="col-3">
<t t-call="survey.survey_progression">
<t t-set="page_ids" t-value="survey.page_ids.ids"/>
<t t-set="page_number" t-value="page_ids.index(page.id) + (1 if survey.progression_mode == 'number' else 0)"/>
</t>
</div>
</div>
</t>
@@ -138,12 +151,19 @@
<input type="hidden" name="question_id" t-att-value="question.id" />
<t t-call="survey.question_container"/>
<div class="text-center mt16 mb256">
<button t-if="survey.users_can_go_back and question != answer.predefined_question_ids[0]" type="submit" class="btn btn-secondary"
name="button_submit" value="previous" t-att-data-previous-page-id="previous_page_id">Previous page</button>
<button type="submit" t-att-value="'next' if not last else 'finish'" class="btn btn-primary">
<t t-esc="'Next Page' if not last else 'Submit Survey'"/>
</button>
<div class="row">
<div class="offset-3 col-6 text-center mt16 mb128">
<button t-if="survey.users_can_go_back and question != answer.predefined_question_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 last else 'finish'" class="btn btn-primary">
Continue</button><span class="font-weight-bold ml-2 text-primary d-none d-md-inline">or press Enter</span>
</div>
<div class="col-3">
<t t-call="survey.survey_progression">
<t t-set="page_ids" t-value="survey.question_ids.ids"/>
<t t-set="page_number" t-value="page_ids.index(question.id)"/>
</t>
</div>
</div>
</t>
</template>
@@ -151,7 +171,7 @@
<!-- Finished (taken and finished) survey page -->
<template id="survey_fill_form_done" name="Survey: finished">
<div class="wrap">
<div class="o_survey_finished mt32 mb32">
<div class="o_survey_finished mt32 mb32 pb256">
<h1>Thank you!</h1>
<div t-field="survey.description_done" class="oe_no_empty" />
<div class="row">
@@ -204,7 +224,10 @@
<!-- Question widgets -->
<template id="question_container" name="Survey: question container">
<t t-set="answer_lines" t-value="answer.user_input_line_ids.filtered(lambda line: line.question_id == question)"/>
<div class="js_question-wrapper" t-att-id="question.id" t-att-data-required="question.constr_mandatory"
<!--Use Key selection if number of choices is < 26 to keep Z for other choice if any-->
<t t-set="letters" t-value="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<t t-set="useKeySelection" t-value="len(question.suggested_answer_ids) &lt; len(letters) and survey.questions_layout == 'page_per_question'"/>
<div class="js_question-wrapper pb-4" t-att-id="question.id" t-att-data-required="question.constr_mandatory"
t-att-data-constr-error-msg="question.constr_error_msg"
t-att-data-validation-error-msg="question.validation_error_msg">
<h4>
@@ -220,26 +243,30 @@
<t t-if="question.question_type == 'simple_choice'"><t t-call="survey.question_simple_choice"/></t>
<t t-if="question.question_type == 'multiple_choice'"><t t-call="survey.question_multiple_choice"/></t>
<t t-if="question.question_type == 'matrix'"><t t-call="survey.question_matrix"/></t>
<div class="o_survey_question_error alert alert-danger" role="alert"></div>
<div class="o_survey_question_error overflow-hidden border-0 py-0 px-3 alert alert-danger" role="alert"></div>
</div>
</template>
<template id="question_text_box" name="Question: free text box">
<textarea class="form-control o_survey_question_text_box" rows="3" t-att-name="question.id"
t-att-data-question-type="question.question_type"><t t-if="answer_lines" t-esc="answer_lines[0].value_text_box or None"/></textarea>
<div class="o_survey_comment_container py-0 px-1">
<textarea class="form-control o_survey_question_text_box font-weight-bold bg-transparent text-primary rounded-0" rows="3" t-att-name="question.id"
t-att-data-question-type="question.question_type"><t t-if="answer_lines" t-esc="answer_lines[0].value_text_box or None"/></textarea>
</div>
</template>
<template id="question_char_box" name="Question: text box">
<input t-att-type="'email' if question.validation_email else 'text'"
class="form-control o_survey_question_text_box" t-att-name="question.id"
<div class="o_survey_comment_container py-0 px-1">
<input t-att-type="'email' if question.validation_email else 'text'"
class="form-control o_survey_question_text_box font-weight-bold bg-transparent text-primary rounded-0" t-att-name="question.id"
t-att-value="answer_lines[0].value_char_box if answer_lines else None"
t-att-data-question-type="question.question_type"
t-att-data-validation-length-min="question.validation_length_min if question.validation_required else False"
t-att-data-validation-length-max="question.validation_length_max if question.validation_required else False"/>
</div>
</template>
<template id="question_numerical_box" name="Question: numerical box">
<input type="number" step="any" class="form-control o_survey_question_numerical_box" t-att-name="question.id" t-att-value="answer_lines[0].value_numerical_box if answer_lines else None"
<input type="number" step="any" class="form-control o_survey_question_numerical_box font-weight-bold bg-transparent text-primary rounded-0" t-att-name="question.id" t-att-value="answer_lines[0].value_numerical_box if answer_lines else None"
t-att-data-question-type="question.question_type"
t-att-data-validation-float-min="question.validation_min_float_value if question.validation_required else False"
t-att-data-validation-float-max="question.validation_max_float_value if question.validation_required else False"/>
@@ -249,12 +276,12 @@
<div class="input-group o_survey_form_date" t-attf-id="datetimepicker_#{question.id}" data-target-input="nearest"
t-att-data-mindate="question.validation_min_date"
t-att-data-maxdate="question.validation_max_date">
<input type="text" class="form-control datetimepicker-input o_survey_question_date"
<input type="text" class="form-control datetimepicker-input o_survey_question_date font-weight-bold bg-transparent text-primary rounded-0"
t-attf-data-target="#datetimepicker_#{question.id}" t-att-name="question.id"
t-att-value="format_date(answer_lines[0].value_date) if answer_lines else None"
t-att-data-question-type="question.question_type"/>
<div t-if="not survey_form_readonly" class="input-group-append" t-attf-data-target="#datetimepicker_#{question.id}" data-toggle="datetimepicker">
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
<div t-if="not survey_form_readonly" class="input-group-append position-absolute" t-attf-data-target="#datetimepicker_#{question.id}" data-toggle="datetimepicker">
<div class="input-group-text text-primary border-0 bg-transparent"><i class="fa fa-calendar"></i></div>
</div>
</div>
</template>
@@ -263,12 +290,12 @@
<div class="input-group o_survey_form_date" t-attf-id="datetimepicker_#{question.id}" data-target-input="nearest"
t-att-data-mindate="question.validation_min_datetime"
t-att-data-maxdate="question.validation_max_datetime">
<input type="text" class="form-control datetimepicker-input o_survey_question_datetime"
<input type="text" class="form-control datetimepicker-input o_survey_question_datetime font-weight-bold bg-transparent text-primary rounded-0"
t-attf-data-target="#datetimepicker_#{question.id}" t-att-name="question.id"
t-att-value="format_datetime(answer_lines[0].value_datetime) if answer_lines else None"
t-att-data-question-type="question.question_type"/>
<div t-if="not survey_form_readonly" class="input-group-append" t-attf-data-target="#datetimepicker_#{question.id}" data-toggle="datetimepicker">
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
<div t-if="not survey_form_readonly" class="input-group-append position-absolute" t-attf-data-target="#datetimepicker_#{question.id}" data-toggle="datetimepicker">
<div class="input-group-text text-primary border-0 bg-transparent"><i class="fa fa-calendar"></i></div>
</div>
</div>
</template>
@@ -279,27 +306,58 @@
<div class="row o_survey_form_choice"
t-att-data-name="question.id"
data-question-type="simple_choice_radio">
<div t-foreach='question.suggested_answer_ids' t-as='label' t-attf-class="col-lg-#{question.column_nb}">
<label t-att-class="' bg-success ' if quizz_correction and label.answer_score > 0.0 else None">
<input type="radio" t-att-value='label.id' class="o_survey_form_choice_item"
t-att-name='question.id'
t-att-checked="answer_line and answer_line.suggested_answer_id.id == label.id and 'checked' or None"/>
<span t-field='label.value'/>
</label>
<t t-set="item_idx" t-value="0"/>
<div class="d-flex flex-wrap font-weight-bold col-lg-12">
<t t-set="has_correct_answer" t-value="scoring_display_correction and any(label.is_correct for label in question.suggested_answer_ids)"/>
<t t-foreach='question.suggested_answer_ids' t-as='label'>
<t t-set="item_idx" t-value="label_index"/>
<t t-set="answer_selected" t-value="answer_line and answer_line.suggested_answer_id.id == label.id"/>
<t t-set="is_correct" t-value="label.answer_score > 0.0"/>
<!--Used for print mode with corrections -->
<t t-set="answer_class" t-if="not has_correct_answer" t-value="''" />
<t t-set="answer_class" t-elif="is_correct" t-value="'bg-success'" />
<t t-set="answer_class" t-elif="not is_correct" t-value="'bg-danger'" />
<label t-att-for="str(question.id) + '_' + str(label.id)"
t-att-class="'o_survey_choice_btn m-1 py-1 px-3 text-white rounded %s %s' % (answer_class, 'o_survey_selected' if answer_selected else '')">
<t t-call="survey.survey_selection_key">
<t t-set="selection_key_class" t-value="'o_survey_radio_btn float-left d-flex'"/>
</t>
<span class="ml-2" t-field='label.value'/>
<input t-att-id="str(question.id) + '_' + str(label.id)" type="radio" t-att-value='label.id' class="o_survey_form_choice_item invisible position-absolute"
t-att-name='question.id'
t-att-checked="answer_line and answer_line.suggested_answer_id.id == label.id and 'checked' or None"
t-att-data-selection-key="letters[item_idx] if useKeySelection else ''"/>
<i class="fa fa-check-circle float-right mt-1 position-relative"></i>
<i class="fa fa-circle float-right mt-1 position-relative"></i>
</label>
</t>
</div>
<div t-if='question.comments_allowed and question.comment_count_as_answer' class="js_comments col-lg-12" >
<label>
<input type="radio" class="o_survey_form_choice_item o_survey_js_form_other_comment" value="-1"
t-att-name='question.id'
t-att-checked="comment_line and 'checked' or None"/>
<span t-field="question.comments_message" />
</label>
<textarea type="text" class="form-control"
t-attf-disabled="#{'' if comment_line else 'disabled'}"><t t-esc="comment_line.value_char_box if comment_line else ''"/></textarea>
<div class="d-flex flex-wrap font-weight-bold">
<label t-att-class="'o_survey_choice_btn m-1 py-1 px-3 text-white rounded %s' % ('o_survey_selected' if comment_line else '')">
<t t-set="item_idx" t-value="item_idx + 1"/>
<t t-call="survey.survey_selection_key">
<t t-set="selection_key_class" t-value="'o_survey_radio_btn float-left d-flex'"/>
</t>
<input type="radio" class="o_survey_form_choice_item o_survey_js_form_other_comment invisible position-absolute" value="-1"
t-att-name='question.id'
t-att-checked="comment_line and 'checked' or None"
t-att-data-selection-key="letters[item_idx] if useKeySelection else ''"/>
<span class="ml-2" t-field="question.comments_message" />
<i class="fa fa-check-circle float-right mt-1 position-relative"></i>
<i class="fa fa-circle float-right mt-1 position-relative"></i>
</label>
</div>
<div class="o_survey_comment_container py-0 px-1">
<textarea type="text" class="form-control o_survey_question_text_box font-weight-bold bg-transparent text-primary rounded-0"
t-att-disabled="None if comment_line else 'disabled'"><t t-esc="comment_line.value_char_box if comment_line else ''"/></textarea>
</div>
</div>
<div t-if='question.comments_allowed and not question.comment_count_as_answer' class="col-lg-12">
<span t-field="question.comments_message"/>
<textarea type="text" class="form-control o_survey_comment"><t t-esc="comment_line.value_char_box if comment_line else ''"/></textarea>
<div t-if='question.comments_allowed and not question.comment_count_as_answer' class="col-lg-12 o_survey_comment_container mx-1 py-0 pl-3 pr-4">
<textarea type="text" class="form-control o_survey_comment o_survey_question_text_box font-weight-bold bg-transparent text-primary rounded-0"
t-att-placeholder="question.comments_message"><t t-esc="comment_line.value_char_box if comment_line else ''"/></textarea>
</div>
</div>
</template>
@@ -309,61 +367,132 @@
<div class="row o_survey_form_choice o_survey_question_multiple_choice"
t-att-data-name="question.id"
t-att-data-question-type="question.question_type">
<div t-foreach='question.suggested_answer_ids' t-as='label' t-attf-class="col-lg-#{question.column_nb}">
<t t-set="answer_line" t-value="answer_lines.filtered(lambda line: line.suggested_answer_id == label)"/>
<label t-att-class="' bg-success ' if quizz_correction and label.answer_score > 0.0 else None">
<input type="checkbox" t-att-value='label.id' class="o_survey_form_choice_item"
t-att-name="question.id"
t-att-checked="'checked' if answer_line else None"/>
<span t-field='label.value'/>
</label>
</div>
<t t-set="item_idx" t-value="0"/>
<div class="d-flex flex-wrap font-weight-bold col-lg-12">
<t t-set="has_correct_answer" t-value="scoring_display_correction and any(label.is_correct for label in question.suggested_answer_ids)"/>
<t t-foreach='question.suggested_answer_ids' t-as='label'>
<t t-set="item_idx" t-value="label_index"/>
<t t-set="answer_line" t-value="answer_lines.filtered(lambda line: line.suggested_answer_id == label)"/>
<t t-set="answer_selected" t-value="answer_line and answer_line.suggested_answer_id.id == label.id"/>
<t t-set="is_correct" t-value="label.answer_score > 0.0"/>
<div t-if='question.comments_allowed and question.comment_count_as_answer' class="js_ck_comments col-lg-12" >
<label>
<input type="checkbox" class="o_survey_form_choice_item o_survey_js_form_other_comment" value="-1"
t-att-name="question.id"
t-att-checked="comment_line and 'checked' or None"/>
<span t-field="question.comments_message" />
</label>
<textarea type="text" class="form-control"
t-attf-disabled="#{'' if comment_line else 'disabled'}"><t t-esc="comment_line.value_char_box if comment_line else ''"/></textarea>
<!--Used for print mode with corrections -->
<t t-set="answer_class" t-if="not has_correct_answer" t-value="''" />
<t t-set="answer_class" t-elif="is_correct" t-value="'bg-success'" />
<t t-set="answer_class" t-elif="not is_correct" t-value="'bg-danger'" />
<label t-att-class="'o_survey_choice_btn m-1 py-1 px-3 text-white rounded %s %s' % (answer_class, 'o_survey_selected' if answer_line else '')">
<t t-call="survey.survey_selection_key">
<t t-set="selection_key_class" t-value="'float-left d-flex'"/>
</t>
<input type="checkbox" t-att-value='label.id' class="o_survey_form_choice_item invisible position-absolute"
t-att-name="question.id"
t-att-checked="'checked' if answer_line else None"
t-att-data-selection-key="letters[item_idx] if useKeySelection else ''"/>
<span class="ml-2" t-field='label.value'/>
<i class="fa fa-check-square float-right mt-1 position-relative"></i>
<i class="fa fa-square float-right mt-1 position-relative"></i>
</label>
</t>
</div>
<div t-if='question.comments_allowed and not question.comment_count_as_answer' class="col-lg-12">
<span t-field="question.comments_message"/>
<textarea type="text" class="form-control o_survey_comment"><t t-esc="comment_line.value_char_box if comment_line else ''"/></textarea>
<div t-if='question.comments_allowed and question.comment_count_as_answer' class="js_ck_comments col-lg-12" >
<div class="d-flex flex-wrap font-weight-bold">
<label t-att-class="'o_survey_choice_btn m-1 py-1 px-3 text-white rounded %s' % ('o_survey_selected' if comment_line else '')">
<t t-set="item_idx" t-value="item_idx + 1"/>
<t t-call="survey.survey_selection_key">
<t t-set="selection_key_class" t-value="'float-left d-flex'"/>
</t>
<input type="checkbox" class="o_survey_form_choice_item o_survey_js_form_other_comment invisible position-absolute" value="-1"
t-att-name="question.id"
t-att-checked="comment_line and 'checked' or None"
t-att-data-selection-key="letters[item_idx] if useKeySelection else ''"/>
<span class="ml-2" t-field="question.comments_message" />
<i class="fa fa-check-square float-right mt-1 position-relative"></i>
<i class="fa fa-square float-right mt-1 position-relative"></i>
</label>
</div>
<div class="o_survey_comment_container py-0 px-1">
<textarea type="text" class="form-control o_survey_question_text_box font-weight-bold bg-transparent text-primary rounded-0"
t-att-disabled="None if comment_line else 'disabled'"><t t-esc="comment_line.value_char_box if comment_line else ''"/></textarea>
</div>
</div>
<div t-if='question.comments_allowed and not question.comment_count_as_answer' class="col-lg-12 o_survey_comment_container mx-1 py-0 pl-3 pr-4">
<textarea type="text" class="form-control o_survey_comment o_survey_question_text_box font-weight-bold bg-transparent text-primary rounded-0"
t-att-placeholder="question.comments_message"><t t-esc="comment_line.value_char_box if comment_line else ''"/></textarea>
</div>
</div>
</template>
<template id="question_matrix" name="Question: matrix">
<t t-set="comment_line" t-value="answer_lines.filtered(lambda line: line.value_char_box)"/>
<table class="table table-hover o_survey_question_matrix"
<table class="table table-hover o_survey_question_matrix text-white text-center mb-0"
t-att-data-name="question.id"
t-att-data-question-type="question.question_type"
t-att-data-sub-questions="question.matrix_row_ids.ids">
<thead>
<tr>
<th> </th>
<th t-foreach="question.suggested_answer_ids" t-as="col_label"><span t-field="col_label.value" /></th>
<th class="border-0"> </th>
<th class="border-0" t-foreach="question.suggested_answer_ids" t-as="col_label"><span t-field="col_label.value" /></th>
</tr>
</thead>
<tbody>
<tr t-foreach="question.matrix_row_ids" t-as="row_label">
<th><span t-field="row_label.value" /></th>
<td t-foreach="question.suggested_answer_ids" t-as="col_label">
<t t-set="item_idx" t-value="0"/>
<tr class="bg-white text-white" t-foreach="question.matrix_row_ids" t-as="row_label">
<th class="border-0"><span t-field="row_label.value" /></th>
<t t-foreach="question.suggested_answer_ids" t-as="col_label">
<t t-set="answer" t-value="answer_lines.filtered(lambda line: line.suggested_answer_id == col_label and line.matrix_row_id == row_label)"/>
<input t-att-type="'checkbox' if question.matrix_subtype == 'multiple' else 'radio'"
t-att-name="'%s_%s' % (question.id, row_label.id)" t-att-value='col_label.id'
t-att-checked="'checked' if answer else None"
t-att-data-row-id="row_label.id"/>
</td>
<td t-att-class="'o_survey_matrix_btn text-primary border-0 %s'
% ('o_survey_selected' if answer else '')">
<input t-att-type="'checkbox' if question.matrix_subtype == 'multiple' else 'radio'"
t-att-name="'%s_%s' % (question.id, row_label.id)" t-att-value='col_label.id'
t-att-checked="'checked' if answer else None"
t-att-data-row-id="row_label.id"
t-att-data-selection-key="letters[item_idx] if useKeySelection else ''"
class="o_survey_form_choice_item d-none"/>
<i t-att-class="'o_survey_matrix_empty_checkbox fa fa-%s position-relative'
% ('square-o' if question.matrix_subtype == 'multiple' else 'circle-thin')"></i>
<i t-att-class="'fa fa-check-%s position-relative'
% ('square' if question.matrix_subtype == 'multiple' else 'circle')"></i>
<t t-call="survey.survey_selection_key">
<t t-set="selection_key_class"
t-value="'float-right font-weight-bold %s' % ('o_survey_radio_btn' if question.matrix_subtype != 'multiple' else '')"/>
</t>
<t t-set="item_idx" t-value="item_idx + 1"/>
</td>
</t>
</tr>
</tbody>
</table>
<div t-if='question.comments_allowed'>
<span t-field="question.comments_message"/>
<textarea type="text" class="form-control" t-att-name="'%s_%s' % (question.id, 'comment')"><t t-esc="comment_line.value_char_box if comment_line else ''"/></textarea>
<textarea type="text" class="form-control o_survey_question_text_box o_survey_comment font-weight-bold bg-transparent text-primary rounded-0"
t-att-placeholder="question.comments_message"
t-att-name="'%s_%s' % (question.id, 'comment')"><t t-esc="comment_line.value_char_box if comment_line else ''"/></textarea>
</div>
</template>
<template id="survey_selection_key">
<div t-if="useKeySelection" t-att-class="'o_survey_choice_key bg-white position-relative rounded %s' % selection_key_class">
<span class="o_survey_key text-center position-absolute bg-white rounded-left py-0 pr-4 pl-2"><span class="text-primary text-center text-center w-100 position-relative">Key</span></span>
<span class="text-primary text-center w-100 position-relative" t-esc="letters[item_idx]"/>
</div>
</template>
<template id="survey_progression" name="Survey: Progression">
<div class="float-right">
<t t-if="len(page_ids) > 1">
<t t-set="percentage" t-value="round(100*(page_number/len(page_ids)))"/>
<t t-if="survey.progression_mode == 'percent'">
<span class="o_survey_progress_percent" t-esc="percentage"/> % completed
</t>
<t t-else="">
<span class="o_survey_progress_number" t-esc="page_number"/> of <span t-esc="len(page_ids)"/>
<span t-if="survey.questions_layout == 'page_per_question'">answered</span>
<span t-else="">pages</span>
</t>
<div class="o_survey_progress progress flex-grow-1">
<div class="progress-bar bg-primary" t-att-style="'width: ' + str(percentage) + '%'"/>
</div>
</t>
</div>
</template>
</data>
@@ -74,7 +74,7 @@
<!-- ============================================================ -->
<template id="survey_button_form_view" name="Survey: back to form view">
<div groups="survey.group_survey_manager" t-ignore="true" class="alert alert-info alert-dismissible rounded-0 mt16 fade show d-print-none css_editable_mode_hidden">
<div groups="survey.group_survey_manager" t-ignore="true" class="alert alert-info alert-dismissible rounded-0 fade show d-print-none css_editable_mode_hidden">
<div t-ignore="true" class="text-center">
<a t-attf-href="/web#view_type=form&amp;model=survey.survey&amp;id=#{survey.id}&amp;action=survey.action_survey_form"><i class="fa fa-fw fa-arrow-right"/><span t-if="answer and answer.test_entry">This is a test survey. </span>Edit Survey.</a>
</div>
@@ -6,7 +6,7 @@
<t t-call="survey.layout">
<t t-set="survey_form_readonly" t-value="true"/>
<div class="wrap">
<div class="container">
<div class="o_survey_print container">
<t t-if="answer.test_entry" t-call="survey.survey_button_form_view" />
<div class='jumbotron mt32'>
<h1><span t-field='survey.title'/></h1>
@@ -27,7 +27,7 @@
<h2>
<span t-field='question.title'/>
<span t-if="question.constr_mandatory" class="text-danger">*</span>
<span t-if="quizz_correction" class="badge badge-pill" t-att-data-score-question="question.id"></span>
<span t-if="scoring_display_correction" class="badge badge-pill" t-att-data-score-question="question.id"></span>
</h2>
<t t-if="question.description"><div class="text-muted oe_no_empty" t-field='question.description'/></t>
<t t-if="question.question_type == 'text_box'"><t t-call="survey.question_text_box"/></t>
@@ -38,7 +38,7 @@
<t t-if="question.question_type == 'simple_choice'"><t t-call="survey.question_simple_choice"/></t>
<t t-if="question.question_type == 'multiple_choice'"><t t-call="survey.question_multiple_choice"/></t>
<t t-if="question.question_type == 'matrix'"><t t-call="survey.question_matrix"/></t>
<div class="o_survey_question_error alert alert-danger" role="alert"></div>
<div class="o_survey_question_error overflow-hidden border-0 py-0 px-3 alert alert-danger" role="alert"></div>
</div>
</t>
</t>
@@ -89,16 +89,16 @@ var failCertificationSteps = [{
trigger: 'button:contains("Start Certification")'
}, { // Question: What type of wood is the best for furniture?
content: 'Survey: selecting answer "Fir"',
trigger: 'div.js_question-wrapper:contains("What type of wood is the best for furniture") label:contains("Fir") input',
trigger: 'div.js_question-wrapper:contains("What type of wood is the best for furniture") label:contains("Fir")'
}, { // Question: Select all the furniture shown in the video
content: 'Survey: ticking answer "Table"',
trigger: 'div.js_question-wrapper:contains("Select all the furniture shown in the video") label:contains("Table") input'
trigger: 'div.js_question-wrapper:contains("Select all the furniture shown in the video") label:contains("Table")'
}, {
content: 'Survey: ticking answer "Bed"',
trigger: 'div.js_question-wrapper:contains("Select all the furniture shown in the video") label:contains("Bed") input'
trigger: 'div.js_question-wrapper:contains("Select all the furniture shown in the video") label:contains("Bed")'
}, {
content: 'Survey: submitting the certification with wrong answers',
trigger: 'button:contains("Submit Survey")'
trigger: 'button:contains("Submit")'
}];
var retrySteps = [{
@@ -111,19 +111,19 @@ var succeedCertificationSteps = [{
trigger: 'button:contains("Start Certification")'
}, { // Question: What type of wood is the best for furniture?
content: 'Survey: selecting answer "Oak"',
trigger: 'div.js_question-wrapper:contains("What type of wood is the best for furniture") label:contains("Oak") input',
trigger: 'div.js_question-wrapper:contains("What type of wood is the best for furniture") label:contains("Oak")',
}, { // Question: Select all the furniture shown in the video
content: 'Survey: ticking answer "Chair"',
trigger: 'div.js_question-wrapper:contains("Select all the furniture shown in the video") label:contains("Chair") input'
trigger: 'div.js_question-wrapper:contains("Select all the furniture shown in the video") label:contains("Chair")'
}, {
content: 'Survey: ticking answer "Shelve"',
trigger: 'div.js_question-wrapper:contains("Select all the furniture shown in the video") label:contains("Shelve") input'
trigger: 'div.js_question-wrapper:contains("Select all the furniture shown in the video") label:contains("Shelve")'
}, {
content: 'Survey: ticking answer "Desk"',
trigger: 'div.js_question-wrapper:contains("Select all the furniture shown in the video") label:contains("Desk") input'
trigger: 'div.js_question-wrapper:contains("Select all the furniture shown in the video") label:contains("Desk")'
}, {
content: 'Survey: submitting the certification with correct answers',
trigger: 'button:contains("Submit Survey")'
trigger: 'button:contains("Submit")'
}];
var certificationCompletionSteps = [{