diff --git a/addons/web/static/src/views/fields/email/email_field.js b/addons/web/static/src/views/fields/email/email_field.js
index d97742001b1..550ac5b6814 100644
--- a/addons/web/static/src/views/fields/email/email_field.js
+++ b/addons/web/static/src/views/fields/email/email_field.js
@@ -16,6 +16,12 @@ export class EmailField extends Component {
EmailField.template = "web.EmailField";
EmailField.props = {
...standardFieldProps,
+ placeholder: { type: String, optional: true },
+};
+EmailField.extractProps = (fieldName, record, attrs) => {
+ return {
+ placeholder: attrs.placeholder,
+ };
};
EmailField.displayName = _lt("Email");
diff --git a/addons/web/static/src/views/fields/float/float_field.js b/addons/web/static/src/views/fields/float/float_field.js
index 81b73a7b767..e23967678a0 100644
--- a/addons/web/static/src/views/fields/float/float_field.js
+++ b/addons/web/static/src/views/fields/float/float_field.js
@@ -53,6 +53,7 @@ FloatField.props = {
step: { type: Number, optional: true },
digits: { type: Array, optional: true },
invalidate: { type: Function, optional: true },
+ placeholder: { type: String, optional: true },
};
FloatField.defaultProps = {
inputType: "text",
@@ -73,6 +74,7 @@ FloatField.extractProps = (fieldName, record, attrs) => {
digits:
(attrs.digits ? JSON.parse(attrs.digits) : attrs.options.digits) ||
record.fields[fieldName].digits,
+ placeholder: attrs.placeholder,
};
};
diff --git a/addons/web/static/src/views/fields/float/float_field.xml b/addons/web/static/src/views/fields/float/float_field.xml
index 4b5739405eb..6d424f2d55e 100644
--- a/addons/web/static/src/views/fields/float/float_field.xml
+++ b/addons/web/static/src/views/fields/float/float_field.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/addons/web/static/src/views/fields/float_time/float_time_field.js b/addons/web/static/src/views/fields/float_time/float_time_field.js
index 5f7377de69a..bdd517b0810 100644
--- a/addons/web/static/src/views/fields/float_time/float_time_field.js
+++ b/addons/web/static/src/views/fields/float_time/float_time_field.js
@@ -29,6 +29,7 @@ FloatTimeField.template = "web.FloatTimeField";
FloatTimeField.props = {
...standardFieldProps,
invalidate: { type: Function, optional: true },
+ placeholder: { type: String, optional: true },
};
FloatTimeField.defaultProps = {
invalidate: () => {},
@@ -38,9 +39,10 @@ FloatTimeField.displayName = _lt("Time");
FloatTimeField.supportedTypes = ["float"];
FloatTimeField.isEmpty = () => false;
-FloatTimeField.extractProps = (fieldName, record) => {
+FloatTimeField.extractProps = (fieldName, record, attrs) => {
return {
invalidate: () => record.setInvalidField(fieldName),
+ placeholder: attrs.placeholder,
};
};
diff --git a/addons/web/static/src/views/fields/float_time/float_time_field.xml b/addons/web/static/src/views/fields/float_time/float_time_field.xml
index ee0425821b2..eee4e55bd5d 100644
--- a/addons/web/static/src/views/fields/float_time/float_time_field.xml
+++ b/addons/web/static/src/views/fields/float_time/float_time_field.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/addons/web/static/src/views/fields/percentage/percentage_field.js b/addons/web/static/src/views/fields/percentage/percentage_field.js
index 01998ad62f9..798ae0a025b 100644
--- a/addons/web/static/src/views/fields/percentage/percentage_field.js
+++ b/addons/web/static/src/views/fields/percentage/percentage_field.js
@@ -32,6 +32,7 @@ PercentageField.props = {
...standardFieldProps,
invalidate: { type: Function, optional: true },
digits: { type: Array, optional: true },
+ placeholder: { type: String, optional: true },
};
PercentageField.defaultProps = {
invalidate: () => {},
@@ -46,6 +47,7 @@ PercentageField.extractProps = (fieldName, record, attrs) => {
digits:
(attrs.digits ? JSON.parse(attrs.digits) : attrs.options.digits) ||
record.fields[fieldName].digits,
+ placeholder: attrs.placeholder,
};
};
diff --git a/addons/web/static/src/views/fields/percentage/percentage_field.xml b/addons/web/static/src/views/fields/percentage/percentage_field.xml
index 412075a8b30..eb275d200d4 100644
--- a/addons/web/static/src/views/fields/percentage/percentage_field.xml
+++ b/addons/web/static/src/views/fields/percentage/percentage_field.xml
@@ -10,6 +10,7 @@
diff --git a/addons/web/static/tests/views/fields/char_field_tests.js b/addons/web/static/tests/views/fields/char_field_tests.js
index 8ac4b4f9e42..5291e664384 100644
--- a/addons/web/static/tests/views/fields/char_field_tests.js
+++ b/addons/web/static/tests/views/fields/char_field_tests.js
@@ -1015,4 +1015,27 @@ QUnit.module("Fields", (hooks) => {
);
}
);
+
+ QUnit.test("char field with placeholder", async function (assert) {
+ serverData.models.partner.fields.foo.default = false;
+
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ serverData,
+ arch: `
+
`,
+ });
+
+ assert.strictEqual(
+ target.querySelector(".o_field_widget[name='foo'] input").placeholder,
+ "Placeholder"
+ );
+ });
});
diff --git a/addons/web/static/tests/views/fields/email_field_tests.js b/addons/web/static/tests/views/fields/email_field_tests.js
index ad8dcdb02d3..13d588a5633 100644
--- a/addons/web/static/tests/views/fields/email_field_tests.js
+++ b/addons/web/static/tests/views/fields/email_field_tests.js
@@ -163,11 +163,12 @@ QUnit.module("Fields", (hooks) => {
`,
});
+ assert.strictEqual(target.querySelector(".o_field_email input").placeholder, "Placeholder");
await click(target.querySelector(".o_form_button_save"));
const mailtoLink = target.querySelector(".o_field_email a");
@@ -239,4 +240,27 @@ QUnit.module("Fields", (hooks) => {
);
}
);
+
+ QUnit.test("email field with placeholder", async function (assert) {
+ serverData.models.partner.fields.foo.default = false;
+
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ serverData,
+ arch: `
+ `,
+ });
+
+ assert.strictEqual(
+ target.querySelector(".o_field_widget[name='foo'] input").placeholder,
+ "New Placeholder"
+ );
+ });
});
diff --git a/addons/web/static/tests/views/fields/float_field_tests.js b/addons/web/static/tests/views/fields/float_field_tests.js
index f0eb5a3e06e..fdd46a67990 100644
--- a/addons/web/static/tests/views/fields/float_field_tests.js
+++ b/addons/web/static/tests/views/fields/float_field_tests.js
@@ -1,9 +1,9 @@
/** @odoo-module **/
-import { registry } from "@web/core/registry";
import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
-import { click, editInput, getFixture } from "@web/../tests/helpers/utils";
+import { click, editInput, getFixture, triggerEvent } from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
+import { registry } from "@web/core/registry";
let serverData;
let target;
@@ -447,4 +447,21 @@ QUnit.module("Fields", (hooks) => {
"Float value must be formatted if input type isn't number."
);
});
+
+ QUnit.test("float_field field with placeholder", async function (assert) {
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ serverData,
+ arch: '',
+ });
+
+ const input = target.querySelector(".o_field_widget[name='float_field'] input");
+ input.value = "";
+ await triggerEvent(input, null, "input");
+ assert.strictEqual(
+ target.querySelector(".o_field_widget[name='float_field'] input").placeholder,
+ "Placeholder"
+ );
+ });
});
diff --git a/addons/web/static/tests/views/fields/float_time_field_tests.js b/addons/web/static/tests/views/fields/float_time_field_tests.js
index dae0322b3da..e8aada0d09d 100644
--- a/addons/web/static/tests/views/fields/float_time_field_tests.js
+++ b/addons/web/static/tests/views/fields/float_time_field_tests.js
@@ -1,6 +1,6 @@
/** @odoo-module **/
-import { click, editInput, getFixture } from "@web/../tests/helpers/utils";
+import { click, editInput, getFixture, triggerEvent } from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
let serverData;
@@ -164,4 +164,24 @@ QUnit.module("Fields", (hooks) => {
"date field should not be displayed as invalid now"
);
});
+
+ QUnit.test("float_time field with placeholder", async function (assert) {
+ await makeView({
+ serverData,
+ type: "form",
+ resModel: "partner",
+ arch: `
+ `,
+ });
+
+ const input = target.querySelector(".o_field_widget[name='qux'] input");
+ input.value = "";
+ await triggerEvent(input, null, "input");
+ assert.strictEqual(
+ target.querySelector(".o_field_widget[name='qux'] input").placeholder,
+ "Placeholder"
+ );
+ });
});
diff --git a/addons/web/static/tests/views/fields/font_selection_field_tests.js b/addons/web/static/tests/views/fields/font_selection_field_tests.js
index 00aa4b532de..79296eebcda 100644
--- a/addons/web/static/tests/views/fields/font_selection_field_tests.js
+++ b/addons/web/static/tests/views/fields/font_selection_field_tests.js
@@ -36,7 +36,7 @@ QUnit.module("Fields", (hooks) => {
serverData,
type: "form",
resModel: "partner",
- arch: '',
+ arch: '',
});
const options = target.querySelectorAll('.o_field_widget[name="fonts"] option');
@@ -46,6 +46,12 @@ QUnit.module("Fields", (hooks) => {
"Widget font should be default (Lato)"
);
assert.strictEqual(options[0].value, "false", "Unselected option has no value");
+ assert.strictEqual(
+ options[0].textContent,
+ "Placeholder",
+ "Unselected option is the placeholder"
+ );
+
assert.strictEqual(
options[1].style.fontFamily,
"Lato",
diff --git a/addons/web/static/tests/views/fields/integer_field_tests.js b/addons/web/static/tests/views/fields/integer_field_tests.js
index 69e2abb2149..c6001131d7a 100644
--- a/addons/web/static/tests/views/fields/integer_field_tests.js
+++ b/addons/web/static/tests/views/fields/integer_field_tests.js
@@ -2,7 +2,13 @@
import { localization } from "@web/core/l10n/localization";
import { defaultLocalization } from "@web/../tests/helpers/mock_services";
-import { click, editInput, getFixture, patchWithCleanup } from "@web/../tests/helpers/utils";
+import {
+ click,
+ editInput,
+ getFixture,
+ patchWithCleanup,
+ triggerEvent,
+} from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
let serverData;
@@ -299,4 +305,21 @@ QUnit.module("Fields", (hooks) => {
"The new value should be saved and displayed properly."
);
});
+
+ QUnit.test("IntegerField field with placeholder", async function (assert) {
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ serverData,
+ arch: ``,
+ });
+
+ const input = target.querySelector(".o_field_widget[name='int_field'] input");
+ input.value = "";
+ await triggerEvent(input, null, "input");
+ assert.strictEqual(
+ target.querySelector(".o_field_widget[name='int_field'] input").placeholder,
+ "Placeholder"
+ );
+ });
});
diff --git a/addons/web/static/tests/views/fields/many2many_tags_field_tests.js b/addons/web/static/tests/views/fields/many2many_tags_field_tests.js
index 28d922be56a..dc128a7bf6d 100644
--- a/addons/web/static/tests/views/fields/many2many_tags_field_tests.js
+++ b/addons/web/static/tests/views/fields/many2many_tags_field_tests.js
@@ -1525,4 +1525,19 @@ QUnit.module("Fields", (hooks) => {
);
assert.containsOnce(row, ".o-autocomplete--input");
});
+
+ QUnit.test("Many2ManyTagsField with placeholder", async function (assert) {
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ serverData,
+ arch:
+ '',
+ });
+
+ assert.strictEqual(
+ target.querySelector(".o_field_widget[name='timmy'] input").placeholder,
+ "Placeholder"
+ );
+ });
});
diff --git a/addons/web/static/tests/views/fields/many2one_avatar_field_tests.js b/addons/web/static/tests/views/fields/many2one_avatar_field_tests.js
index 57eab4a0de8..2b50d291e06 100644
--- a/addons/web/static/tests/views/fields/many2one_avatar_field_tests.js
+++ b/addons/web/static/tests/views/fields/many2one_avatar_field_tests.js
@@ -224,4 +224,19 @@ QUnit.module("Fields", (hooks) => {
"/web/image/user/17/avatar_128"
);
});
+
+ QUnit.test("Many2OneAvatar with placeholder", async function (assert) {
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ serverData,
+ arch:
+ '',
+ });
+
+ assert.strictEqual(
+ target.querySelector(".o_field_widget[name='user_id'] input").placeholder,
+ "Placeholder"
+ );
+ });
});
diff --git a/addons/web/static/tests/views/fields/many2one_field_tests.js b/addons/web/static/tests/views/fields/many2one_field_tests.js
index eda9e043815..ad2493a15e7 100644
--- a/addons/web/static/tests/views/fields/many2one_field_tests.js
+++ b/addons/web/static/tests/views/fields/many2one_field_tests.js
@@ -4034,4 +4034,18 @@ QUnit.module("Fields", (hooks) => {
"Should contain 2 breadcrumbs after the clicking on the link"
);
});
+
+ QUnit.test("Many2oneField with placeholder", async function (assert) {
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ serverData,
+ arch: '',
+ });
+
+ assert.strictEqual(
+ target.querySelector(".o_field_widget[name='trululu'] input").placeholder,
+ "Placeholder"
+ );
+ });
});
diff --git a/addons/web/static/tests/views/fields/monetary_field_tests.js b/addons/web/static/tests/views/fields/monetary_field_tests.js
index 626fa70e415..9e9aeb4c557 100644
--- a/addons/web/static/tests/views/fields/monetary_field_tests.js
+++ b/addons/web/static/tests/views/fields/monetary_field_tests.js
@@ -9,6 +9,7 @@ import {
editInput,
getFixture,
patchWithCleanup,
+ triggerEvent,
} from "@web/../tests/helpers/utils";
import { session } from "@web/session";
@@ -1112,4 +1113,25 @@ QUnit.module("Fields", (hooks) => {
"The currency symbol is not displayed"
);
});
+
+ QUnit.test("monetary field with placeholder", async function (assert) {
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ serverData,
+ arch: `
+ `,
+ });
+
+ const input = target.querySelector(".o_field_widget[name='float_field'] input");
+ input.value = "";
+ await triggerEvent(input, null, "input");
+ assert.strictEqual(
+ target.querySelector(".o_field_widget[name='float_field'] input").placeholder,
+ "Placeholder"
+ );
+ });
});
diff --git a/addons/web/static/tests/views/fields/percentage_field_tests.js b/addons/web/static/tests/views/fields/percentage_field_tests.js
index 15d8d3f5ede..be247ae8ea0 100644
--- a/addons/web/static/tests/views/fields/percentage_field_tests.js
+++ b/addons/web/static/tests/views/fields/percentage_field_tests.js
@@ -1,6 +1,6 @@
/** @odoo-module **/
-import { click, editInput, getFixture } from "@web/../tests/helpers/utils";
+import { click, editInput, getFixture, triggerEvent } from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
let serverData;
@@ -77,4 +77,24 @@ QUnit.module("Fields", (hooks) => {
"The new value should be formatted properly."
);
});
+
+ QUnit.test("percentage field with placeholder", async function (assert) {
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ serverData,
+ arch: `
+ `,
+ });
+
+ const input = target.querySelector(".o_field_widget[name='float_field'] input");
+ input.value = "";
+ await triggerEvent(input, null, "input");
+ assert.strictEqual(
+ target.querySelector(".o_field_widget[name='float_field'] input").placeholder,
+ "Placeholder"
+ );
+ });
});
diff --git a/addons/web/static/tests/views/fields/phone_field_tests.js b/addons/web/static/tests/views/fields/phone_field_tests.js
index a62de608ddc..351107b3e84 100644
--- a/addons/web/static/tests/views/fields/phone_field_tests.js
+++ b/addons/web/static/tests/views/fields/phone_field_tests.js
@@ -162,4 +162,27 @@ QUnit.module("Fields", (hooks) => {
"foo should be focused"
);
});
+
+ QUnit.test("phone field with placeholder", async function (assert) {
+ serverData.models.partner.fields.foo.default = false;
+
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ serverData,
+ arch: `
+ `,
+ });
+
+ assert.strictEqual(
+ target.querySelector(".o_field_widget[name='foo'] input").placeholder,
+ "Placeholder"
+ );
+ });
});
diff --git a/addons/web/static/tests/views/fields/selection_field_tests.js b/addons/web/static/tests/views/fields/selection_field_tests.js
index 7ee429a9330..9d5c586d1ab 100644
--- a/addons/web/static/tests/views/fields/selection_field_tests.js
+++ b/addons/web/static/tests/views/fields/selection_field_tests.js
@@ -395,4 +395,22 @@ QUnit.module("Fields", (hooks) => {
"Two options in second selection as it is now required"
);
});
+
+ QUnit.test("selection field with placeholder", async function (assert) {
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ serverData,
+ arch: `
+ `,
+ });
+
+ const placeholderOption = target.querySelector(
+ ".o_field_widget[name='trululu'] select option"
+ );
+ assert.strictEqual(placeholderOption.textContent, "Placeholder");
+ assert.strictEqual(placeholderOption.value, "false");
+ });
});
diff --git a/addons/web/static/tests/views/fields/url_field_tests.js b/addons/web/static/tests/views/fields/url_field_tests.js
index 94593192269..45f47e8bc3d 100644
--- a/addons/web/static/tests/views/fields/url_field_tests.js
+++ b/addons/web/static/tests/views/fields/url_field_tests.js
@@ -296,4 +296,27 @@ QUnit.module("Fields", (hooks) => {
target.querySelector(".o_field_widget[name=foo]").textContent
);
});
+
+ QUnit.test("url field with placeholder", async function (assert) {
+ serverData.models.partner.fields.foo.default = false;
+
+ await makeView({
+ type: "form",
+ resModel: "partner",
+ serverData,
+ arch: `
+ `,
+ });
+
+ assert.strictEqual(
+ target.querySelector(".o_field_widget[name='foo'] input").placeholder,
+ "Placeholder"
+ );
+ });
});