[IMP] survey_extra_fields : handle file question on page navigation
Some checks failed
pre-commit / pre-commit (pull_request) Has been cancelled

(cherry picked from commit d0afa2310d)
This commit is contained in:
2026-06-10 17:23:31 +02:00
parent 5c0962c1fd
commit 1cece14084
6 changed files with 149 additions and 16 deletions

View File

@@ -6,6 +6,68 @@ odoo.define("survey_extra_fields.survey_form", function (require) {
var survey_form = require("survey.form");
survey_form.include({
/**
* @override
* Bind delegated listeners on the form root so they keep working after
* each page is re-rendered (the inner content is replaced on navigation,
* but the root element persists). They let the user clear a selected
* file before submitting the form.
*/
start: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
self.$el.on(
"change.surveyExtraFile",
'input[data-question-type="file"]',
self._onFileInputChange.bind(self)
);
self.$el.on(
"click.surveyExtraFile",
".o_survey_file_clear",
self._onFileClearClick.bind(self)
);
});
},
/**
* On selection, show the file "chip" (filename + remove button) and hide
* the raw input, so a freshly selected file looks exactly like an already
* stored one (rendered server-side when navigating back).
*/
_onFileInputChange: function (ev) {
var input = ev.currentTarget;
var $container = $(input).closest(".o_survey_comment_container");
if (!$container.length || !(input.files && input.files.length > 0)) {
return;
}
$container.find(".o_survey_file_name").text(input.files[0].name);
$container.find(".o_survey_file_selected").removeClass("d-none");
delete input.dataset.fileCleared;
$(input).addClass("d-none");
},
/**
* Discard the current file: hide the chip and bring back the input so the
* user can pick a new one. A file already stored server-side is only
* really replaced once a new file is submitted (see save_lines).
*/
_onFileClearClick: function (ev) {
ev.preventDefault();
var $container = $(ev.currentTarget).closest(".o_survey_comment_container");
if (!$container.length) {
return;
}
var $input = $container.find('input[data-question-type="file"]');
if ($input.length) {
$input.val("");
// Flag the explicit removal so the submit tells the server to
// drop any previously stored file (instead of preserving it).
$input[0].dataset.fileCleared = "1";
$input.removeClass("d-none");
}
$container.find(".o_survey_file_selected").addClass("d-none");
},
_readFileAsDataURL: function (file) {
return new Promise(function (resolve, reject) {
var reader = new FileReader();
@@ -19,18 +81,28 @@ odoo.define("survey_extra_fields.survey_form", function (require) {
});
},
/**
* @override
* The base implementation builds the submit params synchronously and
* fires the RPC immediately. File inputs need to be read asynchronously
* (FileReader), so when the current page contains file answers we
* replicate the submit flow here, injecting the base64 file payload
* before submitting.
*/
_submitForm: function (options) {
var self = this;
var $fileInputs = this.$('input[data-question-type="file"]');
var hasFiles = false;
// A file action is either a new selection or an explicit removal of
// a previously stored file (which must be communicated to the server).
var hasFileAction = false;
$fileInputs.each(function () {
if (this.files && this.files.length > 0) {
hasFiles = true;
if ((this.files && this.files.length > 0) || this.dataset.fileCleared) {
hasFileAction = true;
return false;
}
});
if (!hasFiles || this.options.isStartScreen) {
if (!hasFileAction || this.options.isStartScreen) {
return this._super(options);
}
@@ -51,7 +123,9 @@ odoo.define("survey_extra_fields.survey_form", function (require) {
this._prepareSubmitValues(formData, params);
// Read all selected files as base64
// Read all selected files as base64 and add them to the submit
// params. Explicitly cleared inputs (no new file) send a "cleared"
// sentinel so the server removes the previously stored file.
var filePromises = [];
$fileInputs.each(function () {
if (this.files && this.files.length > 0) {
@@ -65,6 +139,8 @@ odoo.define("survey_extra_fields.survey_form", function (require) {
});
})
);
} else if (this.dataset.fileCleared) {
params[this.name] = JSON.stringify({ cleared: true });
}
});
@@ -107,7 +183,16 @@ odoo.define("survey_extra_fields.survey_form", function (require) {
$questionWrapper.data("constrErrorMsg") ||
_t("This question requires an answer.");
if (questionRequired && !(this.files && this.files.length > 0)) {
errors[questionId] = constrErrorMsg;
// A file may already be stored server-side (e.g. uploaded
// then navigating back): the chip is visible even though the
// input is empty. Treat that as a valid answer.
var $chip = $(this)
.closest(".o_survey_comment_container")
.find(".o_survey_file_selected");
var hasExistingFile = $chip.length && !$chip.hasClass("d-none");
if (!hasExistingFile) {
errors[questionId] = constrErrorMsg;
}
return;
}
if (this.files && this.files.length > 0) {
@@ -143,4 +228,4 @@ odoo.define("survey_extra_fields.survey_form", function (require) {
return result;
},
});
});
});