[REF] survey : make survey form a widget
Purpose of this commit is - to make the survey js controller a widget, to be more 'odoo-standard'. - to lessen number of RPCs and clean old code for prefilling and validation of surveys. Clean Prefill : Prefill can be done directly in the template as the template has already all the needed values (in answer object). Clean Validation : Validation is done at server side and is independant from prefilling values. Dates: Dates are now formatted directly in the template, at rendering, using a format_date fonction pointer called in the template. Don't use widget in review mode: Remove o_survey_form class from review template as only dates were processed in the widget for review template. Breadcrumb : Remove button previous, prev=prev and go_back mechanism : The previous page is handled by the breadcrump Remove redirect url mechanism. Breadcrumb now saves the answers when going to a previous page. Move o_survey_form class to a higher div to englobe breadcrumb in the widget and ease his handling. Remove locale load as already done in session.js#load_modules This commit modifies the route type of survey submit to work in json. The js survey form controller calls now manually the route via rpc. Task ID : 1930132 PR #32419
This commit is contained in:
@@ -13,7 +13,7 @@ from odoo import fields, http, _
|
||||
from odoo.addons.base.models.ir_ui_view import keep_query
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.http import request, content_disposition
|
||||
from odoo.tools import ustr
|
||||
from odoo.tools import ustr, format_datetime, format_date
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -223,219 +223,125 @@ class Survey(http.Controller):
|
||||
else:
|
||||
return request.redirect('/survey/fill/%s/%s' % (survey_sudo.access_token, answer_sudo.token))
|
||||
|
||||
# Survey direct link to a specific page
|
||||
@http.route('/survey/page/<string:survey_token>/<string:answer_token>/<int:page_id>',
|
||||
type='http', auth='public', website=True)
|
||||
def survey_change_page(self, survey_token, answer_token, page_id, **post):
|
||||
""" Method called when the user switches from one page to another using the breadcrumbs links
|
||||
in the survey layout.
|
||||
TODO: Right now, the answers that are not submitted are LOST when changing from one page to another
|
||||
using this method.
|
||||
|
||||
The survey "submit" mechanism needs to be refactored entirely to make this more user-friendly."""
|
||||
# Controls if the survey can be displayed
|
||||
access_data = self._get_access_data(survey_token, answer_token, ensure_token=False)
|
||||
if access_data['validity_code'] is not True:
|
||||
return self._redirect_with_error(access_data, access_data['validity_code'])
|
||||
|
||||
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
|
||||
|
||||
return request.render('survey.survey', {
|
||||
'survey': survey_sudo,
|
||||
'page': request.env['survey.question'].sudo().browse(page_id),
|
||||
'answer': answer_sudo
|
||||
})
|
||||
|
||||
@http.route('/survey/fill/<string:survey_token>/<string:answer_token>', type='http', auth='public', website=True)
|
||||
def survey_display_page(self, survey_token, answer_token, prev=None, **post):
|
||||
def survey_display_page(self, survey_token, answer_token, **post):
|
||||
access_data = self._get_access_data(survey_token, answer_token, ensure_token=True)
|
||||
if access_data['validity_code'] is not True:
|
||||
return self._redirect_with_error(access_data, access_data['validity_code'])
|
||||
|
||||
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
|
||||
|
||||
data = {
|
||||
'format_datetime': lambda dt: format_datetime(request.env, dt, dt_format=False),
|
||||
'format_date': lambda date: format_date(request.env, date)
|
||||
}
|
||||
|
||||
page_or_question_key = 'question' if survey_sudo.questions_layout == 'page_per_question' else 'page'
|
||||
|
||||
# Bypass all if page_id is specified (comes from breadcrumb or previous button)
|
||||
if 'previous_page_id' in post:
|
||||
previous_page_or_question_id = int(post['previous_page_id'])
|
||||
new_previous_id = survey_sudo._previous_page_or_question_id(answer_sudo, previous_page_or_question_id)
|
||||
data.update({
|
||||
'survey': survey_sudo,
|
||||
page_or_question_key: request.env['survey.question'].sudo().browse(previous_page_or_question_id),
|
||||
'answer': answer_sudo,
|
||||
'previous_page_id': new_previous_id
|
||||
})
|
||||
return request.render('survey.survey', data)
|
||||
|
||||
if survey_sudo.is_time_limited and not answer_sudo.start_datetime:
|
||||
# init start date when user starts filling in the survey
|
||||
answer_sudo.write({
|
||||
'start_datetime': fields.Datetime.now()
|
||||
})
|
||||
|
||||
page_or_question_key = 'question' if survey_sudo.questions_layout == 'page_per_question' else 'page'
|
||||
# Select the right page
|
||||
if answer_sudo.state == 'new': # First page
|
||||
page_or_question_id, last = survey_sudo.next_page_or_question(answer_sudo, 0, go_back=False)
|
||||
data = {
|
||||
page_or_question_id, is_last = survey_sudo.next_page_or_question(answer_sudo, 0)
|
||||
data.update({
|
||||
'survey': survey_sudo,
|
||||
page_or_question_key: page_or_question_id,
|
||||
'answer': answer_sudo
|
||||
}
|
||||
if last:
|
||||
'answer': answer_sudo,
|
||||
})
|
||||
if is_last:
|
||||
data.update({'last': True})
|
||||
return request.render('survey.survey', data)
|
||||
elif answer_sudo.state == 'done': # Display success message
|
||||
return request.render('survey.sfinished', self._prepare_survey_finished_values(survey_sudo, answer_sudo))
|
||||
elif answer_sudo.state == 'skip':
|
||||
flag = (True if prev and prev == 'prev' else False)
|
||||
page_or_question_id, last = survey_sudo.next_page_or_question(answer_sudo, answer_sudo.last_displayed_page_id.id, go_back=flag)
|
||||
page_or_question_id, is_last = survey_sudo.next_page_or_question(answer_sudo, answer_sudo.last_displayed_page_id.id)
|
||||
previous_id = survey_sudo._previous_page_or_question_id(answer_sudo, page_or_question_id.id)
|
||||
|
||||
#special case if you click "previous" from the last page, then leave the survey, then reopen it from the URL, avoid crash
|
||||
if not page_or_question_id:
|
||||
page_or_question_id, last = survey_sudo.next_page_or_question(answer_sudo, answer_sudo.last_displayed_page_id.id, go_back=True)
|
||||
|
||||
data = {
|
||||
data.update({
|
||||
'survey': survey_sudo,
|
||||
page_or_question_key: page_or_question_id,
|
||||
'answer': answer_sudo
|
||||
}
|
||||
if last:
|
||||
'answer': answer_sudo,
|
||||
'previous_page_id': previous_id
|
||||
})
|
||||
if is_last:
|
||||
data.update({'last': True})
|
||||
|
||||
return request.render('survey.survey', data)
|
||||
else:
|
||||
return request.render("survey.403", {'survey': survey_sudo})
|
||||
|
||||
@http.route('/survey/prefill/<string:survey_token>/<string:answer_token>', type='http', auth='public', website=True)
|
||||
def survey_get_answers(self, survey_token, answer_token, page_or_question_id=None, **post):
|
||||
""" TDE NOTE: original comment: # AJAX prefilling of a survey -> AJAX / http ?? """
|
||||
access_data = self._get_access_data(survey_token, answer_token, ensure_token=True)
|
||||
if access_data['validity_code'] is not True and access_data['validity_code'] != 'answer_done':
|
||||
return {}
|
||||
|
||||
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
|
||||
try:
|
||||
page_or_question_id = int(page_or_question_id)
|
||||
except:
|
||||
page_or_question_id = None
|
||||
|
||||
# Fetch previous answers
|
||||
if survey_sudo.questions_layout == 'one_page' or not page_or_question_id:
|
||||
previous_answers = answer_sudo.user_input_line_ids
|
||||
elif survey_sudo.questions_layout == 'page_per_section':
|
||||
previous_answers = answer_sudo.user_input_line_ids.filtered(lambda line: line.page_id.id == page_or_question_id)
|
||||
else:
|
||||
previous_answers = answer_sudo.user_input_line_ids.filtered(lambda line: line.question_id.id == page_or_question_id)
|
||||
|
||||
# Return non empty answers in a JSON compatible format
|
||||
ret = {}
|
||||
for answer in previous_answers:
|
||||
if not answer.skipped:
|
||||
answer_tag = '%s_%s' % (answer.survey_id.id, answer.question_id.id)
|
||||
answer_value = None
|
||||
if answer.answer_type == 'free_text':
|
||||
answer_value = answer.value_free_text
|
||||
elif answer.answer_type == 'text' and answer.question_id.question_type == 'textbox':
|
||||
answer_value = answer.value_text
|
||||
elif answer.answer_type == 'text' and answer.question_id.question_type != 'textbox':
|
||||
# here come comment answers for matrices, simple choice and multiple choice
|
||||
answer_tag = "%s_%s" % (answer_tag, 'comment')
|
||||
answer_value = answer.value_text
|
||||
elif answer.answer_type == 'number':
|
||||
answer_value = str(answer.value_number)
|
||||
elif answer.answer_type == 'date':
|
||||
answer_value = fields.Datetime.to_string(answer.value_date)
|
||||
elif answer.answer_type == 'datetime':
|
||||
answer_value = fields.Datetime.to_string(answer.value_datetime)
|
||||
elif answer.answer_type == 'suggestion' and not answer.value_suggested_row:
|
||||
answer_value = answer.value_suggested.id
|
||||
elif answer.answer_type == 'suggestion' and answer.value_suggested_row:
|
||||
answer_tag = "%s_%s" % (answer_tag, answer.value_suggested_row.id)
|
||||
answer_value = answer.value_suggested.id
|
||||
if answer_value:
|
||||
ret.setdefault(answer_tag, []).append(answer_value)
|
||||
else:
|
||||
_logger.warning("[survey] No answer has been found for question %s marked as non skipped" % answer_tag)
|
||||
return json.dumps(ret, default=str)
|
||||
|
||||
@http.route('/survey/scores/<string:survey_token>/<string:answer_token>', type='http', auth='public', website=True)
|
||||
def survey_get_scores(self, survey_id, answer_token, page_id=None, **post):
|
||||
""" TDE NOTE: original comment: # AJAX scores loading for quiz correction mode -> AJAX / http ?? """
|
||||
access_data = self._get_access_data(survey_id, answer_token, ensure_token=True)
|
||||
if access_data['validity_code'] is not True:
|
||||
return {}
|
||||
|
||||
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
|
||||
|
||||
# Compute score for each question
|
||||
ret = {}
|
||||
for answer in answer_sudo.user_input_line_ids:
|
||||
tmp_score = ret.get(answer.question_id.id, 0.0)
|
||||
ret.update({answer.question_id.id: tmp_score + answer.answer_score})
|
||||
return json.dumps(ret)
|
||||
|
||||
@http.route('/survey/submit/<string:survey_token>/<string:answer_token>', type='http', methods=['POST'], auth='public', website=True)
|
||||
@http.route('/survey/submit/<string:survey_token>/<string:answer_token>', type='json', auth='public', website=True)
|
||||
def survey_submit(self, survey_token, answer_token, **post):
|
||||
""" Submit a page from the survey.
|
||||
This will take into account the validation errors and store the answers to the questions.
|
||||
If the time limit is reached, errors will be skipped, answers wil be ignored and
|
||||
survey state will be forced to 'done'
|
||||
|
||||
TDE NOTE: original comment: # AJAX submission of a page -> AJAX / http ?? """
|
||||
If the time limit is reached, errors will be skipped, answers will be ignored and
|
||||
survey state will be forced to 'done'"""
|
||||
# Validation
|
||||
access_data = self._get_access_data(survey_token, answer_token, ensure_token=True)
|
||||
if access_data['validity_code'] is not True:
|
||||
return {}
|
||||
|
||||
return {
|
||||
'error': access_data['validity_code'],
|
||||
}
|
||||
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
|
||||
|
||||
questions, page_or_question_id = survey_sudo._get_survey_questions(answer=answer_sudo,
|
||||
page_id=post.get('page_id'),
|
||||
question_id=post.get('question_id'))
|
||||
|
||||
errors = {}
|
||||
for question in questions:
|
||||
answer_tag = "%s_%s" % (survey_sudo.id, question.id)
|
||||
errors.update(question.validate_question(post, answer_tag))
|
||||
|
||||
if errors:
|
||||
return {
|
||||
'error': 'validation',
|
||||
'fields': errors,
|
||||
}
|
||||
|
||||
if not answer_sudo.test_entry and not survey_sudo._has_attempts_left(answer_sudo.partner_id, answer_sudo.email, answer_sudo.invite_token):
|
||||
# prevent cheating with users creating multiple 'user_input' before their last attempt
|
||||
return {}
|
||||
|
||||
if survey_sudo.questions_layout == 'page_per_section':
|
||||
page_id = int(post['page_id'])
|
||||
questions = request.env['survey.question'].sudo().search([('survey_id', '=', survey_sudo.id), ('page_id', '=', page_id)])
|
||||
# we need the intersection of the questions of this page AND the questions prepared for that user_input
|
||||
# (because randomized surveys do not use all the questions of every page)
|
||||
questions = questions & answer_sudo.question_ids
|
||||
page_or_question_id = page_id
|
||||
elif survey_sudo.questions_layout == 'page_per_question':
|
||||
question_id = int(post['question_id'])
|
||||
questions = request.env['survey.question'].sudo().browse(question_id)
|
||||
page_or_question_id = question_id
|
||||
else:
|
||||
questions = survey_sudo.question_ids
|
||||
questions = questions & answer_sudo.question_ids
|
||||
|
||||
errors = {}
|
||||
# Answer validation
|
||||
# Submitting survey
|
||||
if not answer_sudo.is_time_limit_reached:
|
||||
for question in questions:
|
||||
answer_tag = "%s_%s" % (survey_sudo.id, question.id)
|
||||
errors.update(question.validate_question(post, answer_tag))
|
||||
request.env['survey.user_input_line'].sudo().save_lines(answer_sudo.id, question, post, answer_tag)
|
||||
|
||||
ret = {}
|
||||
if len(errors):
|
||||
# Return errors messages to webpage
|
||||
ret['errors'] = errors
|
||||
else:
|
||||
if not answer_sudo.is_time_limit_reached:
|
||||
for question in questions:
|
||||
answer_tag = "%s_%s" % (survey_sudo.id, question.id)
|
||||
request.env['survey.user_input_line'].sudo().save_lines(answer_sudo.id, question, post, answer_tag)
|
||||
if answer_sudo.is_time_limit_reached or survey_sudo.questions_layout == 'one_page':
|
||||
answer_sudo._mark_done()
|
||||
elif 'previous_page_id' in post:
|
||||
# Go back to specific page using the breadcrumb. Lines are saved and survey continues
|
||||
return '/survey/fill/%s/%s?previous_page_id=%s' % (survey_sudo.access_token, answer_token, post['previous_page_id'])
|
||||
elif 'button_submit' in post:
|
||||
next_page, unused = request.env['survey.survey'].next_page_or_question(answer_sudo, page_or_question_id)
|
||||
vals = {'last_displayed_page_id': page_or_question_id}
|
||||
|
||||
vals = {}
|
||||
if answer_sudo.is_time_limit_reached or survey_sudo.questions_layout == 'one_page':
|
||||
go_back = False
|
||||
if next_page is None:
|
||||
answer_sudo._mark_done()
|
||||
elif 'button_submit' in post:
|
||||
go_back = post['button_submit'] == 'previous'
|
||||
next_page, last = request.env['survey.survey'].next_page_or_question(answer_sudo, page_or_question_id, go_back=go_back)
|
||||
vals = {'last_displayed_page_id': page_or_question_id}
|
||||
|
||||
if next_page is None and not go_back:
|
||||
answer_sudo._mark_done()
|
||||
else:
|
||||
vals.update({'state': 'skip'})
|
||||
|
||||
if 'breadcrumb_redirect' in post:
|
||||
ret['redirect'] = post['breadcrumb_redirect']
|
||||
else:
|
||||
if vals:
|
||||
answer_sudo.write(vals)
|
||||
vals.update({'state': 'skip'})
|
||||
|
||||
ret['redirect'] = '/survey/fill/%s/%s' % (survey_sudo.access_token, answer_token)
|
||||
if go_back:
|
||||
ret['redirect'] += '?prev=prev'
|
||||
answer_sudo.write(vals)
|
||||
|
||||
return json.dumps(ret)
|
||||
return '/survey/fill/%s/%s' % (survey_sudo.access_token, answer_token)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# COMPLETED SURVEY ROUTES
|
||||
@@ -460,8 +366,9 @@ class Survey(http.Controller):
|
||||
'review': review,
|
||||
'survey': survey_sudo,
|
||||
'answer': answer_sudo,
|
||||
'page_nr': 0,
|
||||
'quizz_correction': survey_sudo.scoring_type != 'scoring_without_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)
|
||||
})
|
||||
|
||||
@http.route('/survey/results/<model("survey.survey"):survey>', type='http', auth='user', website=True)
|
||||
def survey_report(self, survey, answer_token=None, **post):
|
||||
|
||||
@@ -348,19 +348,42 @@ class Survey(models.Model):
|
||||
# ------------------------------------------------------------
|
||||
# ACTIONS
|
||||
# ------------------------------------------------------------
|
||||
@api.model
|
||||
def _get_pages_or_questions(self, user_input):
|
||||
if self.questions_layout == 'one_page':
|
||||
return None
|
||||
elif self.questions_layout == 'page_per_question' and self.questions_selection == 'random':
|
||||
return list(enumerate(
|
||||
user_input.question_ids
|
||||
))
|
||||
else:
|
||||
return list(enumerate(
|
||||
self.question_ids if self.questions_layout == 'page_per_question' else self.page_ids
|
||||
))
|
||||
|
||||
@api.model
|
||||
def next_page_or_question(self, user_input, page_or_question_id, go_back=False):
|
||||
def _previous_page_or_question_id(self, user_input, page_or_question_id):
|
||||
survey = user_input.survey_id
|
||||
pages_or_questions = survey._get_pages_or_questions(user_input)
|
||||
|
||||
current_page_index = pages_or_questions.index(next(p for p in pages_or_questions if p[1].id == page_or_question_id))
|
||||
# is first page
|
||||
if current_page_index == 0:
|
||||
return None
|
||||
|
||||
previous_page_id = pages_or_questions[current_page_index - 1][1].id
|
||||
|
||||
return previous_page_id
|
||||
|
||||
|
||||
@api.model
|
||||
def next_page_or_question(self, user_input, page_or_question_id):
|
||||
""" The next page to display to the user, knowing that page_id is the id
|
||||
of the last displayed page.
|
||||
|
||||
If page_id == 0, it will always return the first page of the survey.
|
||||
|
||||
If all the pages have been displayed and go_back == False, it will
|
||||
return None
|
||||
|
||||
If go_back == True, it will return the *previous* page instead of the
|
||||
next page.
|
||||
If all the pages have been displayed, it will return None
|
||||
|
||||
.. note::
|
||||
It is assumed here that a careful user will not try to set go_back
|
||||
@@ -369,16 +392,9 @@ class Survey(models.Model):
|
||||
"""
|
||||
survey = user_input.survey_id
|
||||
|
||||
if survey.questions_layout == 'one_page':
|
||||
pages_or_questions = survey._get_pages_or_questions(user_input)
|
||||
if not pages_or_questions:
|
||||
return (None, False)
|
||||
elif survey.questions_layout == 'page_per_question' and survey.questions_selection == 'random':
|
||||
pages_or_questions = list(enumerate(
|
||||
user_input.question_ids
|
||||
))
|
||||
else:
|
||||
pages_or_questions = list(enumerate(
|
||||
survey.question_ids if survey.questions_layout == 'page_per_question' else survey.page_ids
|
||||
))
|
||||
|
||||
# First page
|
||||
if page_or_question_id == 0:
|
||||
@@ -387,18 +403,35 @@ class Survey(models.Model):
|
||||
current_page_index = pages_or_questions.index(next(p for p in pages_or_questions if p[1].id == page_or_question_id))
|
||||
|
||||
# All the pages have been displayed
|
||||
if current_page_index == len(pages_or_questions) - 1 and not go_back:
|
||||
if current_page_index == len(pages_or_questions) - 1:
|
||||
return (None, False)
|
||||
# Let's get back, baby!
|
||||
elif go_back and survey.users_can_go_back:
|
||||
return (pages_or_questions[current_page_index - 1][1], False)
|
||||
else:
|
||||
# This will show the last page
|
||||
if current_page_index == len(pages_or_questions) - 2:
|
||||
return (pages_or_questions[current_page_index + 1][1], True)
|
||||
# This will show a regular page
|
||||
else:
|
||||
return (pages_or_questions[current_page_index + 1][1], False)
|
||||
show_last = current_page_index == len(pages_or_questions) - 2
|
||||
return (pages_or_questions[current_page_index + 1][1], show_last)
|
||||
|
||||
def _get_survey_questions(self, answer=None, page_id=None, question_id=None):
|
||||
questions, page_or_question_id = None, None
|
||||
|
||||
if self.questions_layout == 'page_per_section':
|
||||
if not page_id:
|
||||
raise ValueError("Page id is needed for question layout 'page_per_section'")
|
||||
page_id = int(page_id)
|
||||
questions = self.env['survey.question'].sudo().search([('survey_id', '=', self.id), ('page_id', '=', page_id)])
|
||||
page_or_question_id = page_id
|
||||
elif self.questions_layout == 'page_per_question':
|
||||
if not question_id:
|
||||
raise ValueError("Question id is needed for question layout 'page_per_question'")
|
||||
question_id = int(question_id)
|
||||
questions = self.env['survey.question'].sudo().browse(question_id)
|
||||
page_or_question_id = question_id
|
||||
else:
|
||||
questions = self.question_ids
|
||||
|
||||
# we need the intersection of the questions of this page AND the questions prepared for that user_input
|
||||
# (because randomized surveys do not use all the questions of every page)
|
||||
if answer:
|
||||
questions = questions & answer.question_ids
|
||||
return questions, page_or_question_id
|
||||
|
||||
def action_draft(self):
|
||||
self.write({'state': 'draft'})
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
odoo.define('survey.survey', function (require) {
|
||||
'use strict';
|
||||
|
||||
require('web.dom_ready');
|
||||
var core = require('web.core');
|
||||
var time = require('web.time');
|
||||
var ajax = require('web.ajax');
|
||||
var field_utils = require('web.field_utils');
|
||||
|
||||
var _t = core._t;
|
||||
/*
|
||||
* This file is intended to add interactivity to survey forms
|
||||
*/
|
||||
|
||||
var the_form = $('.js_surveyform');
|
||||
|
||||
if(!the_form.length) {
|
||||
return Promise.reject("DOM doesn't contain '.js_surveyform'");
|
||||
}
|
||||
|
||||
var prefill_controller = the_form.attr("data-prefill");
|
||||
var submit_controller = the_form.attr("data-submit");
|
||||
var scores_controller = the_form.attr("data-scores");
|
||||
var print_mode = false;
|
||||
var quiz_correction_mode = false;
|
||||
|
||||
// Printing mode: will disable all the controls in the form
|
||||
if (_.isUndefined(submit_controller)) {
|
||||
$(".js_surveyform .input-group-text span.fa-calendar").css("pointer-events", "none");
|
||||
$('.js_surveyform :input').prop('disabled', true);
|
||||
print_mode = true;
|
||||
}
|
||||
|
||||
// Quiz correction mode
|
||||
if (! _.isUndefined(scores_controller)) {
|
||||
quiz_correction_mode = true;
|
||||
}
|
||||
|
||||
|
||||
// Custom code for right behavior of radio buttons with comments box
|
||||
$('.js_comments>input[type="text"]').focusin(function(){
|
||||
$(this).prev().find('>input').attr("checked","checked");
|
||||
});
|
||||
$('.js_radio input[type="radio"][data-oe-survey-otherr!="1"]').click(function(){
|
||||
$(this).closest('.js_radio').find('.js_comments>input[type="text"]').val("");
|
||||
});
|
||||
$('.js_comments input[type="radio"]').click(function(){
|
||||
$(this).closest('.js_comments').find('>input[data-oe-survey-othert="1"]').focus();
|
||||
});
|
||||
// Custom code for right behavior of dropdown menu with comments
|
||||
$('.js_drop input[data-oe-survey-othert="1"]').hide();
|
||||
$('.js_drop select').change(function(){
|
||||
var other_val = $(this).find('.js_other_option').val();
|
||||
if($(this).val() === other_val){
|
||||
$(this).parent().removeClass('col-lg-12').addClass('col-lg-6');
|
||||
$(this).closest('.js_drop').find('input[data-oe-survey-othert="1"]').show().focus();
|
||||
}
|
||||
else{
|
||||
$(this).parent().removeClass('col-lg-6').addClass('col-lg-12');
|
||||
$(this).closest('.js_drop').find('input[data-oe-survey-othert="1"]').val("").hide();
|
||||
}
|
||||
});
|
||||
// Custom code for right behavior of checkboxes with comments box
|
||||
$('.js_ck_comments>input[type="text"]').focusin(function(){
|
||||
$(this).prev().find('>input').attr("checked","checked");
|
||||
});
|
||||
$('.js_ck_comments input[type="checkbox"]').change(function(){
|
||||
if (! $(this).prop("checked")){
|
||||
$(this).closest('.js_ck_comments').find('input[type="text"]').val("");
|
||||
}
|
||||
});
|
||||
|
||||
// Pre-filling of the form with previous answers
|
||||
function prefill(){
|
||||
if (! _.isUndefined(prefill_controller)) {
|
||||
var prefill_def = $.ajax(prefill_controller, {dataType: "json"})
|
||||
.done(function(json_data){
|
||||
_.each(json_data, function(value, key){
|
||||
|
||||
// prefill of text/number/date boxes
|
||||
var input = the_form.find(".form-control[name=" + key + "]");
|
||||
if (input.hasClass('datetimepicker-input')) {
|
||||
// display dates in user timezone
|
||||
var moment_date = field_utils.parse.date(value[0]);
|
||||
if ($(input).closest('.input-group.date').data('questiontype') === 'datetime') {
|
||||
value = field_utils.format.datetime(moment_date, null, {timezone: true});
|
||||
}
|
||||
else {
|
||||
value = field_utils.format.date(moment_date, null, {timezone: true});
|
||||
}
|
||||
}
|
||||
input.val(value);
|
||||
|
||||
// special case for comments under multiple suggestions questions
|
||||
if (_.string.endsWith(key, "_comment") &&
|
||||
(input.parent().hasClass("js_comments") || input.parent().hasClass("js_ck_comments"))) {
|
||||
input.siblings().find('>input').attr("checked","checked");
|
||||
}
|
||||
|
||||
// checkboxes and radios
|
||||
the_form.find("input[name^='" + key + "_'][type='checkbox']").each(function(){
|
||||
$(this).val(value);
|
||||
});
|
||||
the_form.find("input[name=" + key + "][type!='text']").each(function(){
|
||||
$(this).val(value);
|
||||
});
|
||||
});
|
||||
})
|
||||
.fail(function(){
|
||||
console.warn("[survey] Unable to load prefill data");
|
||||
});
|
||||
return prefill_def;
|
||||
}
|
||||
}
|
||||
|
||||
// Display score if quiz correction mode
|
||||
function display_scores(){
|
||||
if (! _.isUndefined(scores_controller)) {
|
||||
var score_def = $.ajax(scores_controller, {dataType: "json"})
|
||||
.done(function(json_data){
|
||||
_.each(json_data, function(value, key){
|
||||
the_form.find("span[data-score-question=" + key + "]").text("Your score: " + value);
|
||||
});
|
||||
})
|
||||
.fail(function(){
|
||||
console.warn("[survey] Unable to load score data");
|
||||
});
|
||||
return score_def;
|
||||
}
|
||||
}
|
||||
|
||||
$('.o_survey_header .breadcrumb-item a').each(function() {
|
||||
var $valObj = $(this);
|
||||
$valObj.click(function(event) {
|
||||
event.preventDefault();
|
||||
$('<input>').attr({type: 'hidden', name: 'breadcrumb_redirect', value: $valObj.attr('href')}).appendTo('.js_surveyform');
|
||||
$('.js_surveyform').submit();
|
||||
});
|
||||
});
|
||||
|
||||
// Parameters for form submission
|
||||
$('.js_surveyform').ajaxForm({
|
||||
url: submit_controller,
|
||||
type: 'POST', // submission type
|
||||
dataType: 'json', // answer expected type
|
||||
beforeSubmit: function(formData, $form, options){ // hide previous errmsg before resubmitting
|
||||
var date_fields = $form.find('div.date > input.form-control');
|
||||
for (var i=0; i < date_fields.length; i++) {
|
||||
var el = date_fields[i];
|
||||
var questiontype = $(el).closest('.input-group.date').data('questiontype');
|
||||
var moment_date = questiontype === 'datetime' ? field_utils.parse.datetime(el.value, null, {timezone: true}) : field_utils.parse.date(el.value);
|
||||
|
||||
var field_obj = _.findWhere(formData, {'name': el.name});
|
||||
field_obj.value = moment_date ? moment_date.toJSON() : '';
|
||||
}
|
||||
$('.js_errzone').html("").hide();
|
||||
},
|
||||
success: function(response, status, xhr, wfe){ // submission attempt
|
||||
if(_.has(response, 'errors')){ // some questions have errors
|
||||
_.each(_.keys(response.errors), function(key){
|
||||
$("#" + key + '>.js_errzone').append('<p>' + response.errors[key] + '</p>').show();
|
||||
if (_.keys(response.errors)[_.keys(response.errors).length - 1] === key) {
|
||||
$('html, body').animate({
|
||||
scrollTop: $('.js_errzone:visible:first').closest('.js_question-wrapper').offset().top - $('.o_main_navbar').height()
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
else if (_.has(response, 'redirect')){ // form is ok
|
||||
window.location.replace(response.redirect);
|
||||
return true;
|
||||
}
|
||||
else { // server sends bad data
|
||||
console.error("Incorrect answer sent by server");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
timeout: 5000,
|
||||
error: function(jqXHR, textStatus, errorThrown){ // failure of AJAX request
|
||||
$('#AJAXErrorModal').modal('show');
|
||||
}
|
||||
});
|
||||
|
||||
function load_locale(){
|
||||
var url = "/web/webclient/locale/" + (document.documentElement.getAttribute('lang') || 'en_US').replace('-', '_');
|
||||
return ajax.loadJS(url);
|
||||
}
|
||||
|
||||
// datetimepicker use moment locale to display date format according to language
|
||||
// frontend does not load moment locale at all.
|
||||
// so wait until DOM ready with locale then init datetimepicker
|
||||
load_locale().then(function(){
|
||||
_.each($('.input-group.date'), function(date_field){
|
||||
var disabledDates = []
|
||||
if ($(date_field).data('mindate')) {
|
||||
var minDate = field_utils.format.datetime(moment($(date_field).data('mindate')), null, {timezone: true});
|
||||
}
|
||||
if ($(date_field).data('maxdate')) {
|
||||
var maxDate = field_utils.format.datetime(moment($(date_field).data('maxdate')), null, {timezone: true});
|
||||
}
|
||||
else {
|
||||
var minDate = $(date_field).data('mindate') || moment({ y: 1900 });
|
||||
var maxDate = $(date_field).data('maxdate') || moment().add(200, "y");
|
||||
}
|
||||
var datetimepickerFormat = time.getLangDateFormat()
|
||||
if ($(date_field).data('questiontype') === 'datetime') {
|
||||
datetimepickerFormat = time.getLangDatetimeFormat()
|
||||
}
|
||||
else{
|
||||
maxDate = moment(maxDate).add(1, "d");
|
||||
disabledDates = [maxDate]
|
||||
}
|
||||
$('#' + date_field.id).datetimepicker({
|
||||
format : datetimepickerFormat,
|
||||
minDate: minDate,
|
||||
maxDate: maxDate,
|
||||
disabledDates: disabledDates,
|
||||
useCurrent: false,
|
||||
viewDate: moment(new Date()).hours(0).minutes(0).seconds(0).milliseconds(0),
|
||||
calendarWeeks: true,
|
||||
icons: {
|
||||
time: 'fa fa-clock-o',
|
||||
date: 'fa fa-calendar',
|
||||
next: 'fa fa-chevron-right',
|
||||
previous: 'fa fa-chevron-left',
|
||||
up: 'fa fa-chevron-up',
|
||||
down: 'fa fa-chevron-down',
|
||||
},
|
||||
locale : moment.locale(),
|
||||
allowInputToggle: true,
|
||||
keyBinds: null,
|
||||
});
|
||||
$('#' + date_field.id).on('error.datetimepicker', function () {
|
||||
return false;
|
||||
});
|
||||
});
|
||||
// Launch prefilling
|
||||
prefill();
|
||||
if(quiz_correction_mode === true){
|
||||
display_scores();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,322 @@
|
||||
odoo.define('survey.form', function(require) {
|
||||
'use strict';
|
||||
|
||||
var ajax = require('web.ajax');
|
||||
var field_utils = require('web.field_utils');
|
||||
var publicWidget = require('web.public.widget');
|
||||
var time = require('web.time');
|
||||
|
||||
publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend({
|
||||
selector: '.o_survey_form',
|
||||
events: {
|
||||
'change .o_survey_form_choice_item': '_onChangeChoiceItem',
|
||||
'click button[name="button_submit"]': '_onSubmit',
|
||||
'click .o_survey_header .breadcrumb-item a': '_onBreadcrumbClick',
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Widget
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function() {
|
||||
self.options = self.$target.find('form').data()
|
||||
var $timer = self.$('.o_survey_timer');
|
||||
if ($timer.length) {
|
||||
this.surveyTimerWidget = new publicWidget.registry.SurveyTimerWidget(this);
|
||||
this.surveyTimerWidget.attachTo($timer);
|
||||
this.surveyTimerWidget.on('time_up', this, function (ev) {
|
||||
self.$el.find('button[name="button_submit"]').click();
|
||||
});
|
||||
}
|
||||
|
||||
self.$('div.o_survey_form_date').each(function () {
|
||||
self._initDateTimePicker($(this));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Handlers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* Checks, if the 'other' choice is checked. Applies only if the comment count as answer.
|
||||
* In case of dropdownlist:
|
||||
* If not checked : Clear the comment textarea and hide it
|
||||
* If checked : show the comment textarea and focus on it
|
||||
* In case of radio buttons
|
||||
* If not checked : Clear the comment textarea and disable it
|
||||
* If checked : enable the comment textarea and focus on it
|
||||
*
|
||||
* @private
|
||||
* @param {Event} event
|
||||
*/
|
||||
_onChangeChoiceItem: function (event) {
|
||||
var $choiceItemGroup = $(event.currentTarget).parents('.o_survey_form_choice');
|
||||
var $otherItem = $choiceItemGroup.find('.o_survey_js_form_other_comment');
|
||||
var $commentInput = $choiceItemGroup.find('textarea[type="text"]');
|
||||
|
||||
// Handle dropdownlist
|
||||
var $dropdownlist = $choiceItemGroup.find('select');
|
||||
var isDropdown = $dropdownlist.length > 0
|
||||
|
||||
var isOtherSelected = (isDropdown && $dropdownlist.val() === $otherItem.val()) || $otherItem.prop('checked')
|
||||
|
||||
if (isOtherSelected || $commentInput.hasClass('o_survey_comment')) {
|
||||
if (isDropdown) {
|
||||
$commentInput.toggleClass('d-none', false);
|
||||
}
|
||||
if (isOtherSelected) {
|
||||
$commentInput.enable();
|
||||
$commentInput.focus();
|
||||
}
|
||||
} else {
|
||||
if (isDropdown) {
|
||||
$commentInput.toggleClass('d-none', true);
|
||||
}
|
||||
$commentInput.val('');
|
||||
$commentInput.enable(false);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* When clicking on a breadcrumb enabled item, redirect to the target page.
|
||||
* Uses the submit flow to validate and save the answer before going to the specified page.
|
||||
*
|
||||
* @private
|
||||
* @param {Event} event
|
||||
*/
|
||||
_onBreadcrumbClick: function (event) {
|
||||
event.preventDefault();
|
||||
this._submitForm({'previous_page_id': $(event.currentTarget).data('previousPageId')})
|
||||
},
|
||||
|
||||
_onSubmit: function (event) {
|
||||
event.preventDefault();
|
||||
var $target = $(event.currentTarget);
|
||||
var submitValue = $target.val();
|
||||
this._submitForm({'button_submit': submitValue})
|
||||
},
|
||||
|
||||
_submitForm: function (params) {
|
||||
var self = this;
|
||||
var $form = this.$('form');
|
||||
var formData = new FormData($form[0]);
|
||||
|
||||
this.$('div.o_survey_form_date').each(function () {
|
||||
self._updateDateForSubmit($(this), formData);
|
||||
});
|
||||
this._prepareSubmitValues(formData, params);
|
||||
|
||||
this._resetErrors();
|
||||
|
||||
return self._rpc({
|
||||
route: '/survey/submit/' + self.options['surveyToken'] + '/' + self.options['answerToken'],
|
||||
params: params,
|
||||
}).then(function (result) {
|
||||
return self._onSubmitDone(result, params);
|
||||
});
|
||||
},
|
||||
|
||||
_onSubmitDone: function (result, params) {
|
||||
var self = this;
|
||||
if (result && !result.error) {
|
||||
window.location = result;
|
||||
}
|
||||
else if (result && result.fields && result.error === 'validation') {
|
||||
var fieldKeys = _.keys(result.fields);
|
||||
_.each(fieldKeys, function (key) {
|
||||
self.$("#" + key + '>.o_survey_question_error').append($('<p>', {text: result.fields[key]})).toggleClass('d-none', false);
|
||||
if (fieldKeys[fieldKeys.length - 1] === key) {
|
||||
self._scrollToError(self.$('.o_survey_question_error:visible:first').closest('.js_question-wrapper'));
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
var $target = self.$('.o_survey_error');
|
||||
$target.toggleClass('d-none', false);
|
||||
self._scrollToError($target);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// INIT FIELDS
|
||||
// -------------------------------------------------------------------------
|
||||
/*
|
||||
* Convert the client side date format into server side date format before submit
|
||||
*/
|
||||
_updateDateForSubmit: function (dateGroup, formData) {
|
||||
var input = $(dateGroup).find('input');
|
||||
var dateValue = input.val();
|
||||
var questionType = $(input).closest('.o_survey_form_date').data('questiontype');
|
||||
if (dateValue) {
|
||||
var momentDate = questionType === 'datetime' ? field_utils.parse.datetime(dateValue, null, {timezone: true}) : field_utils.parse.date(dateValue);
|
||||
var newDate = momentDate ? momentDate.toJSON() : '';
|
||||
formData.set(input.attr('name'), newDate);
|
||||
input.val(newDate);
|
||||
}
|
||||
},
|
||||
|
||||
// TOOLS
|
||||
// -------------------------------------------------------------------------
|
||||
_prepareSubmitValues: function (formData, params) {
|
||||
var self = this;
|
||||
formData.forEach(function(value, key){
|
||||
if (value !== -1) {
|
||||
// Handles Comment
|
||||
if (key.indexOf('_comment') !== -1){
|
||||
key = key.split('_comment')[0];
|
||||
value = {'comment': value};
|
||||
}
|
||||
// Handles Matrix - Matrix answer_tag are composed like : 'questionId_rowId_colId'
|
||||
// and are the only ones with this structure.
|
||||
var splitKey = key.split('_');
|
||||
if (splitKey.length === 3 && splitKey[2] === value) {
|
||||
params = self._prepareSubmitMatrix(params, splitKey, value);
|
||||
}
|
||||
// Handles the rest
|
||||
else {
|
||||
params = self._prepareSubmitOther(params, key, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare answer before submitting form if question type is matrix.
|
||||
* This method regroups answers by question and by row to make a object like :
|
||||
* params = { 'matrixQuestionId' : { 'rowId1' : [colId1, colId2,...], 'rowId2' : [colId1, colId3, ...] } }
|
||||
*/
|
||||
_prepareSubmitMatrix: function(params, splitKey, value) {
|
||||
var key = splitKey[0];
|
||||
var rowId = splitKey[1];
|
||||
var colId = splitKey[2];
|
||||
value = key in params ? params[key] : {};
|
||||
if (rowId in value) {
|
||||
value[rowId].push(colId);
|
||||
} else {
|
||||
value[rowId] = [colId];
|
||||
}
|
||||
params[key] = value;
|
||||
return params;
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare answer before submitting form (any kind of answer - except Matrix -).
|
||||
* This method regroups answers by question.
|
||||
* Lonely answer are directly assigned to questionId. Multiple answers are regrouped in an array:
|
||||
* params = { 'questionId1' : lonelyAnswer, 'questionId2' : [multipleAnswer1, multipleAnswer2, ...] }
|
||||
*/
|
||||
_prepareSubmitOther: function(params, key, value) {
|
||||
if (key in params) {
|
||||
if (params[key].constructor === Array) {
|
||||
params[key].push(value);
|
||||
} else {
|
||||
params[key] = [params[key], value];
|
||||
}
|
||||
} else {
|
||||
params[key] = value;
|
||||
}
|
||||
return params;
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert date value in client current timezone (if already answered)
|
||||
*/
|
||||
_formatDateValue: function ($dateGroup) {
|
||||
var input = $dateGroup.find('input');
|
||||
var dateValue = input.val();
|
||||
if (dateValue !== '') {
|
||||
var momentDate = field_utils.parse.date(dateValue);
|
||||
if ($dateGroup.data('questiontype') === 'datetime') {
|
||||
dateValue = field_utils.format.datetime(momentDate, null, {timezone: true});
|
||||
} else {
|
||||
dateValue = field_utils.format.date(momentDate, null, {timezone: true});
|
||||
}
|
||||
}
|
||||
input.val(dateValue);
|
||||
return dateValue
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize datetimepicker in correct format and with constraints
|
||||
*/
|
||||
_initDateTimePicker: function ($dateGroup) {
|
||||
var disabledDates = []
|
||||
|
||||
var minDateData = $dateGroup.data('mindate')
|
||||
var minDate = minDateData ? this._formatDateTime(minDateData) : moment({ y: 1900 });
|
||||
var maxDateData = $dateGroup.data('maxdate')
|
||||
var maxDate = maxDateData ? this._formatDateTime(maxDateData) : moment().add(200, "y");
|
||||
|
||||
var datetimepickerFormat = time.getLangDateFormat()
|
||||
if ($dateGroup.data('questiontype') === 'datetime') {
|
||||
datetimepickerFormat = time.getLangDatetimeFormat()
|
||||
} else {
|
||||
// Include min and max date in selectable values
|
||||
maxDate = moment(maxDate).add(1, "d");
|
||||
minDate = moment(minDate).subtract(1, "d");
|
||||
disabledDates = [minDate, maxDate]
|
||||
}
|
||||
|
||||
$dateGroup.datetimepicker({
|
||||
format : datetimepickerFormat,
|
||||
minDate: minDate,
|
||||
maxDate: maxDate,
|
||||
disabledDates: disabledDates,
|
||||
useCurrent: false,
|
||||
viewDate: moment(new Date()).hours(0).minutes(0).seconds(0).milliseconds(0),
|
||||
calendarWeeks: true,
|
||||
icons: {
|
||||
time: 'fa fa-clock-o',
|
||||
date: 'fa fa-calendar',
|
||||
next: 'fa fa-chevron-right',
|
||||
previous: 'fa fa-chevron-left',
|
||||
up: 'fa fa-chevron-up',
|
||||
down: 'fa fa-chevron-down',
|
||||
},
|
||||
locale : moment.locale(),
|
||||
allowInputToggle: true,
|
||||
});
|
||||
$dateGroup.on('error.datetimepicker', function () {
|
||||
return false;
|
||||
});
|
||||
},
|
||||
|
||||
_formatDateTime: function (datetimeValue){
|
||||
return field_utils.format.datetime(moment(datetimeValue), null, {timezone: true});
|
||||
},
|
||||
|
||||
_scrollToError: function ($target) {
|
||||
var scrollLocation = $target.offset().top;
|
||||
var navbarHeight = $('.o_main_navbar').height();
|
||||
if (navbarHeight != undefined) {
|
||||
scrollLocation -= navbarHeight
|
||||
}
|
||||
$('html, body').animate({
|
||||
scrollTop: scrollLocation
|
||||
}, 500);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clean all form errors in order to clean DOM before a new validation
|
||||
*/
|
||||
_resetErrors: function () {
|
||||
this.$('.o_survey_question_error').empty().toggleClass("d-none", true);
|
||||
this.$('.o_survey_error').toggleClass("d-none", true);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
return publicWidget.registry.SurveyFormWidget;
|
||||
|
||||
});
|
||||
@@ -35,7 +35,7 @@ var updateTimer = function () {
|
||||
$timer.html(formatTime(timeLeftMinutes) + ':' + formatTime(timeLeftSeconds));
|
||||
} else {
|
||||
clearInterval(interval);
|
||||
$('.js_surveyform').submit();
|
||||
$('.o_survey_form').submit();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -215,7 +215,9 @@ class TestSurveyCommon(common.SavepointCase):
|
||||
return self.url_open('/survey/fill/%s/%s' % (survey.access_token, token))
|
||||
|
||||
def _access_submit(self, survey, token, post_data):
|
||||
return self.url_open('/survey/submit/%s/%s' % (survey.access_token, token), data=post_data)
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
url = base_url + '/survey/submit/%s/%s' % (survey.access_token, token)
|
||||
return self.opener.post(url=url, json={'params': post_data})
|
||||
|
||||
def _find_csrf_token(self, text):
|
||||
csrf_token_re = re.compile("(input.+csrf_token.+value=\")([_a-zA-Z0-9]{51})", re.MULTILINE)
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
<script type="text/javascript" src="/web/static/src/js/fields/field_utils.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/survey/static/src/js/survey_result.js" />
|
||||
<script type="text/javascript" src="/survey/static/src/js/survey.js" />
|
||||
<script type="text/javascript" src="/survey/static/src/js/survey_timer.js" />
|
||||
<script type="text/javascript" src="/survey/static/src/js/survey_form.js" />
|
||||
<script type="text/javascript" src="/survey/static/src/js/survey_result.js" />
|
||||
|
||||
<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>
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
<t t-call="survey.layout">
|
||||
<div class="wrap">
|
||||
<div class="oe_structure" id="oe_structure_survey_survey_1"/>
|
||||
<div class="container">
|
||||
<div class="container o_survey_form">
|
||||
<t t-if="answer.test_entry" t-call="survey.back" />
|
||||
<t t-call="survey.survey_header" />
|
||||
<t t-call="survey.page" />
|
||||
@@ -233,7 +233,7 @@
|
||||
<t t-set="can_go_back" t-value="False" />
|
||||
</t>
|
||||
<t t-if="can_go_back">
|
||||
<a t-att-href="'/survey/page/%s/%s/%s' % (survey.access_token, answer.token, breadcrumb_page.id)">
|
||||
<a href="#" t-att-data-previous-page-id="breadcrumb_page.id">
|
||||
<span t-field='breadcrumb_page.title' />
|
||||
</a>
|
||||
</t>
|
||||
@@ -248,18 +248,20 @@
|
||||
|
||||
<!-- A page -->
|
||||
<template id="page" name="Page">
|
||||
<t t-set="survey_form_readonly" t-value="false"/>
|
||||
<t t-if="survey.questions_layout == 'one_page'" t-set="page_or_question_id" t-value="None" />
|
||||
<t t-if="survey.questions_layout == 'page_per_section'" t-set="page_or_question_id" t-value="page.id" />
|
||||
<t t-if="survey.questions_layout == 'page_per_question'" t-set="page_or_question_id" t-value="question.id" />
|
||||
|
||||
<form role="form" method="post" class="js_surveyform" t-att-name="survey.id"
|
||||
t-att-action="'/survey/fill/%s/%s' % (survey.access_token, answer.token)"
|
||||
t-att-data-prefill="'/survey/prefill/%s/%s?page_or_question_id=%s' % (survey.access_token, answer.token, page_or_question_id)"
|
||||
t-att-data-validate="'/survey/validate/%s/%s' % (survey.access_token, answer.token)"
|
||||
t-att-data-submit="'/survey/submit/%s/%s' % (survey.access_token, answer.token)">
|
||||
|
||||
<form role="form" method="post" t-att-name="survey.id"
|
||||
t-att-data-answer-token="answer.token"
|
||||
t-att-data-survey-token="survey.access_token">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="token" t-att-value="answer.token" />
|
||||
|
||||
<div class="o_survey_error alert alert-danger d-none" role="alert">
|
||||
<p>Their was an error during the validation of the survey.</p>
|
||||
</div>
|
||||
<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" />
|
||||
@@ -281,7 +283,8 @@
|
||||
</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">Previous page</button>
|
||||
<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 t-if="not last" type="submit" class="btn btn-primary" name="button_submit" value="next">Next page</button>
|
||||
<button t-if="last" type="submit" class="btn btn-primary" name="button_submit" value="finish">Submit Survey</button>
|
||||
</div>
|
||||
@@ -292,7 +295,8 @@
|
||||
<t t-call="survey.question" />
|
||||
|
||||
<div class="text-center mt16 mb256">
|
||||
<button t-if="survey.users_can_go_back and question != answer.question_ids[0]" type="submit" class="btn btn-secondary" name="button_submit" value="previous">Previous question</button>
|
||||
<button t-if="survey.users_can_go_back and question != answer.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 t-if="not last" type="submit" class="btn btn-primary" name="button_submit" value="next">Next question</button>
|
||||
<button t-if="last" type="submit" class="btn btn-primary" name="button_submit" value="finish">Submit Survey</button>
|
||||
</div>
|
||||
@@ -316,6 +320,7 @@
|
||||
|
||||
<template id="question" name="Question">
|
||||
<t t-set="prefix" t-value="'%s_%s' % (survey.id, question.id)" />
|
||||
<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="prefix">
|
||||
<h4>
|
||||
<span t-field='question.question' />
|
||||
@@ -330,110 +335,131 @@
|
||||
<t t-if="question.question_type == 'simple_choice'"><t t-call="survey.simple_choice"/></t>
|
||||
<t t-if="question.question_type == 'multiple_choice'"><t t-call="survey.multiple_choice"/></t>
|
||||
<t t-if="question.question_type == 'matrix'"><t t-call="survey.matrix"/></t>
|
||||
<div class="js_errzone alert alert-danger" style="display:none;" role="alert"></div>
|
||||
<div class="o_survey_question_error alert alert-danger d-none" role="alert"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Question widgets -->
|
||||
<template id="free_text" name="Free text box">
|
||||
<textarea class="form-control" rows="3" t-att-name="prefix"></textarea>
|
||||
<textarea class="form-control" rows="3" t-att-name="prefix"><t t-if="answer_lines" t-esc="answer_lines[0].value_free_text or None"/></textarea>
|
||||
</template>
|
||||
|
||||
<template id="textbox" name="Text box">
|
||||
<input type="text" class="form-control" t-att-name="prefix"/>
|
||||
<input type="text" class="form-control" t-att-name="prefix" t-att-value="answer_lines[0].value_text if answer_lines else None"/>
|
||||
</template>
|
||||
|
||||
<template id="numerical_box" name="Numerical box">
|
||||
<input type="number" step="any" class="form-control" t-att-name="prefix"/>
|
||||
<input type="number" step="any" class="form-control" t-att-name="prefix" t-att-value="answer_lines[0].value_number if answer_lines else None"/>
|
||||
</template>
|
||||
|
||||
<template id="date" name="Date box">
|
||||
<div class="form-group">
|
||||
<div class="input-group 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" t-attf-data-target="#datetimepicker_#{question.id}" t-att-name="prefix"/>
|
||||
<div 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>
|
||||
<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" t-attf-data-target="#datetimepicker_#{question.id}" t-att-name="prefix"
|
||||
t-att-value="format_date(answer_lines[0].value_date) if answer_lines else None"/>
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="datetime" name="DateTime box">
|
||||
<div class="form-group">
|
||||
<div class="input-group 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"
|
||||
t-att-data-questiontype="question.question_type">
|
||||
<input type="text" class="form-control datetimepicker-input" t-attf-data-target="#datetimepicker_#{question.id}" t-att-name="prefix"/>
|
||||
<div 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>
|
||||
<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"
|
||||
t-att-data-questiontype="question.question_type">
|
||||
<input type="text" class="form-control datetimepicker-input" t-attf-data-target="#datetimepicker_#{question.id}" t-att-name="prefix"
|
||||
t-att-value="format_datetime(answer_lines[0].value_datetime) if answer_lines else None"/>
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="simple_choice" name="Simple choice">
|
||||
<div t-if="question.display_mode == 'dropdown' and answer.token" class="js_drop row">
|
||||
<t t-set="answer_line" t-value="answer_lines.filtered(lambda line: line.value_suggested)"/>
|
||||
<t t-set="comment_line" t-value="answer_lines.filtered(lambda line: line.value_text)"/>
|
||||
<div t-if="question.display_mode == 'dropdown'" class="o_survey_form_choice row">
|
||||
<div class="col-lg-12">
|
||||
<select class="form-control" t-att-name="prefix">
|
||||
<option disabled="1" selected="1" value="">Choose...</option>
|
||||
<select class="form-control o_survey_form_choice_item" t-att-name="prefix">
|
||||
<option disabled="1" selected="1" value=""
|
||||
t-att-selected="not answer_line and (not comment_line or not question.comment_count_as_answer) and 'selected' or None">Choose...</option>
|
||||
<t t-foreach='question.labels_ids' t-as='label'>
|
||||
<option t-att-value='label.id'><t t-esc='label.value'/></option>
|
||||
<option t-att-value='label.id'
|
||||
t-att-selected="answer_line and answer_line.value_suggested == label and 'selected' or None"><t t-esc='label.value'/></option>
|
||||
</t>
|
||||
<t t-if='question.comments_allowed and question.comment_count_as_answer'>
|
||||
<option class="js_other_option" value="-1"><span t-esc="question.comments_message" /></option>
|
||||
<option class="o_survey_js_form_other_comment" value="-1"
|
||||
t-att-selected="comment_line and question.comment_count_as_answer and 'selected' or None">
|
||||
<span t-esc="question.comments_message"/>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<div t-if='question.comments_allowed and question.comment_count_as_answer' class="col-lg-6">
|
||||
<textarea type="text" class="form-control" rows="1" t-att-name="'%s_%s' % (prefix, 'comment')" data-oe-survey-othert="1"/>
|
||||
<textarea type="text" class="form-control"
|
||||
t-attf-class="form-control #{'' if comment_line else 'd-none'}"
|
||||
t-att-name="'%s_%s' % (prefix, 'comment')" data-oe-survey-othert="1"
|
||||
t-att-value="comment_line.value_text if comment_line else ''"/>
|
||||
</div>
|
||||
<div t-if='question.comments_allowed and not question.comment_count_as_answer' class="col-lg-12 mt16">
|
||||
<span t-field="question.comments_message"/>
|
||||
<textarea type="text" class="form-control" rows="1" t-att-name="'%s_%s' % (prefix, 'comment')"/>
|
||||
<textarea type="text" class="form-control o_survey_comment" t-att-name="'%s_%s' % (prefix, 'comment')"
|
||||
t-att-value="comment_line.value_text if comment_line else ''"/>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="question.display_mode == 'columns' or not answer.token" class="row js_radio">
|
||||
<div t-if="question.display_mode == 'columns'" class="row o_survey_form_choice">
|
||||
<div t-foreach='question.labels_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-name="prefix" t-att-value='label.id' />
|
||||
<input type="radio" t-att-name="prefix" t-att-value='label.id' class="o_survey_form_choice_item"
|
||||
t-att-checked="answer_line and answer_line.value_suggested.id == label.id and 'checked' or None"/>
|
||||
<span t-field='label.value'/>
|
||||
</label>
|
||||
</div>
|
||||
<div t-if='question.comments_allowed and question.comment_count_as_answer' class="js_comments col-lg-12" >
|
||||
<label>
|
||||
<input type="radio" t-att-name="prefix" value="-1"/>
|
||||
<input type="radio" class="o_survey_form_choice_item o_survey_js_form_other_comment" t-att-name="prefix" value="-1"
|
||||
t-att-checked="comment_line and 'checked' or None"/>
|
||||
<span t-field="question.comments_message" />
|
||||
</label>
|
||||
<textarea type="text" class="form-control" rows="1" t-att-name="'%s_%s' % (prefix, 'comment')"/>
|
||||
<textarea type="text" class="form-control" rows="1"
|
||||
t-attf-disabled="#{'' if comment_line else 'disabled'}"
|
||||
t-att-name="'%s_%s' % (prefix, 'comment')"/>
|
||||
</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" rows="1" t-att-name="'%s_%s' % (prefix, 'comment')" data-oe-survey-othert="1"/>
|
||||
<textarea type="text" class="form-control o_survey_comment" rows="1" t-att-name="'%s_%s' % (prefix, 'comment')" data-oe-survey-othert="1"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="multiple_choice" name="Multiple choice">
|
||||
<div class="row">
|
||||
<t t-set="comment_line" t-value="answer_lines.filtered(lambda line: line.value_text)"/>
|
||||
<div class="row o_survey_form_choice">
|
||||
<div t-foreach='question.labels_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.value_suggested == label)"/>
|
||||
<label t-att-class="' bg-success ' if quizz_correction and label.answer_score > 0.0 else None">
|
||||
<input type="checkbox" t-att-name="'%s_%s' % (prefix, label.id)" t-att-value='label.id' />
|
||||
<input type="checkbox" t-att-name="'%s_%s' % (prefix, label.id)" t-att-value='label.id' class="o_survey_form_choice_item"
|
||||
t-att-checked="'checked' if answer_line else None"/>
|
||||
<span t-field='label.value'/>
|
||||
</label>
|
||||
</div>
|
||||
<div t-if='question.comments_allowed and question.comment_count_as_answer' class="js_ck_comments col-lg-12" >
|
||||
<label>
|
||||
<input type="checkbox" t-att-name="'%s_%s' % (prefix, -1)" value="-1" />
|
||||
<input type="checkbox" t-att-name="'%s_%s' % (prefix, -1)" class="o_survey_form_choice_item o_survey_js_form_other_comment" value="-1"
|
||||
t-att-checked="'checked' if comment_line else None"/>
|
||||
<span t-field="question.comments_message" />
|
||||
</label>
|
||||
<textarea type="text" class="form-control" rows="1" t-att-name="'%s_%s' % (prefix, 'comment')"/>
|
||||
<textarea type="text" class="form-control"
|
||||
t-attf-disabled="#{'' if comment_line else 'disabled'}"
|
||||
t-att-name="'%s_%s' % (prefix, 'comment')"
|
||||
t-att-value="comment_line.value_text if comment_line else None"/>
|
||||
</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" rows="1" t-att-name="'%s_%s' % (prefix, 'comment')" data-oe-survey-othert="1"/>
|
||||
<textarea type="text" class="form-control" t-att-name="'%s_%s' % (prefix, 'comment')" data-oe-survey-othert="1"
|
||||
t-att-value="comment_line.value_text if comment_line else None"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -450,8 +476,11 @@
|
||||
<tr t-foreach="question.labels_ids_2" t-as="row_label">
|
||||
<th><span t-field="row_label.value" /></th>
|
||||
<td t-foreach="question.labels_ids" t-as="col_label">
|
||||
<input t-if="question.matrix_subtype == 'simple'" type="radio" t-att-name="'%s_%s' % (prefix, row_label.id)" t-att-value='col_label.id' />
|
||||
<input t-if="question.matrix_subtype == 'multiple'" type="checkbox" t-att-name="'%s_%s_%s' % (prefix, row_label.id, col_label.id)" t-att-value='col_label.id' />
|
||||
<t t-set="answer" t-value="answer_lines.filtered(lambda line: line.value_suggested == col_label and line.value_suggested_row == row_label)"/>
|
||||
<input t-if="question.matrix_subtype == 'simple'" type="radio" t-att-name="'%s_%s' % (prefix, row_label.id)" t-att-value='col_label.id'
|
||||
t-att-checked="'checked' if answer else None"/>
|
||||
<input t-if="question.matrix_subtype == 'multiple'" type="checkbox" t-att-name="'%s_%s_%s' % (prefix, row_label.id, col_label.id)" t-att-value='col_label.id'
|
||||
t-att-checked="'checked' if answer else None"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -465,6 +494,7 @@
|
||||
<!-- Printable view of a survey (all pages) -->
|
||||
<template id="survey_print" name="Survey">
|
||||
<t t-call="survey.layout">
|
||||
<t t-set="survey_form_readonly" t-value="true"/>
|
||||
<div class="wrap">
|
||||
<div class="container">
|
||||
<t t-if="answer.test_entry" t-call="survey.back" />
|
||||
@@ -473,34 +503,37 @@
|
||||
<t t-if="survey.description"><div t-field='survey.description' class="oe_no_empty"/></t>
|
||||
<t t-if="review" t-call="survey.retake_survey_button"/>
|
||||
</div>
|
||||
<div role="form" class="js_surveyform" t-att-name="'%s' % (survey.id)" t-att-data-prefill="'/survey/prefill/%s/%s' % (survey.access_token, answer.token)">
|
||||
<t t-foreach='survey.question_and_page_ids' t-as='question'>
|
||||
<hr t-if="question.is_page and question != survey.page_ids[0]" />
|
||||
<div t-if="question.is_page" class="o_page_header">
|
||||
<h1 t-field='question.title' />
|
||||
<div t-if="question.description" t-field='question.description' class="oe_no_empty"/>
|
||||
</div>
|
||||
<t t-if="not question.is_page and not answer or (question in answer.question_ids)" >
|
||||
<t t-set="prefix" t-value="'%s_%s' % (survey.id, question.id)" />
|
||||
<div class="js_question-wrapper" t-att-id="prefix">
|
||||
<h2>
|
||||
<span t-field='question.question' />
|
||||
<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>
|
||||
</h2>
|
||||
<t t-if="question.description"><div class="text-muted oe_no_empty" t-field='question.description'/></t>
|
||||
<t t-if="question.question_type == 'free_text'"><t t-call="survey.free_text"/></t>
|
||||
<t t-if="question.question_type == 'textbox'"><t t-call="survey.textbox"/></t>
|
||||
<t t-if="question.question_type == 'numerical_box'"><t t-call="survey.numerical_box"/></t>
|
||||
<t t-if="question.question_type == 'date'"><t t-call="survey.date"/></t>
|
||||
<t t-if="question.question_type == 'datetime'"><t t-call="survey.datetime"/></t>
|
||||
<t t-if="question.question_type == 'simple_choice'"><t t-call="survey.simple_choice"/></t>
|
||||
<t t-if="question.question_type == 'multiple_choice'"><t t-call="survey.multiple_choice"/></t>
|
||||
<t t-if="question.question_type == 'matrix'"><t t-call="survey.matrix"/></t>
|
||||
<div class="js_errzone alert alert-danger" style="display:none;" role="alert"></div>
|
||||
<div role="form">
|
||||
<fieldset disabled="disabled">
|
||||
<t t-foreach='survey.question_and_page_ids' t-as='question'>
|
||||
<hr t-if="question.is_page and question != survey.page_ids[0]" />
|
||||
<div t-if="question.is_page" class="o_page_header">
|
||||
<h1 t-field='question.title' />
|
||||
<div t-if="question.description" t-field='question.description' class="oe_no_empty"/>
|
||||
</div>
|
||||
<t t-if="not question.is_page and not answer or (question in answer.question_ids)" >
|
||||
<t t-set="prefix" t-value="'%s_%s' % (survey.id, question.id)" />
|
||||
<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="prefix">
|
||||
<h2>
|
||||
<span t-field='question.question' />
|
||||
<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>
|
||||
</h2>
|
||||
<t t-if="question.description"><div class="text-muted oe_no_empty" t-field='question.description'/></t>
|
||||
<t t-if="question.question_type == 'free_text'"><t t-call="survey.free_text"/></t>
|
||||
<t t-if="question.question_type == 'textbox'"><t t-call="survey.textbox"/></t>
|
||||
<t t-if="question.question_type == 'numerical_box'"><t t-call="survey.numerical_box"/></t>
|
||||
<t t-if="question.question_type == 'date'"><t t-call="survey.date"/></t>
|
||||
<t t-if="question.question_type == 'datetime'"><t t-call="survey.datetime"/></t>
|
||||
<t t-if="question.question_type == 'simple_choice'"><t t-call="survey.simple_choice"/></t>
|
||||
<t t-if="question.question_type == 'multiple_choice'"><t t-call="survey.multiple_choice"/></t>
|
||||
<t t-if="question.question_type == 'matrix'"><t t-call="survey.matrix"/></t>
|
||||
<div class="o_survey_question_error alert alert-danger d-none" role="alert"></div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user