summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/web/static/src/js/widgets/signature.js97
-rw-r--r--addons/web/static/src/xml/base.xml6
-rw-r--r--addons/web/static/tests/fields/signature_tests.js99
-rw-r--r--addons/web/views/webclient_templates.xml1
4 files changed, 203 insertions, 0 deletions
diff --git a/addons/web/static/src/js/widgets/signature.js b/addons/web/static/src/js/widgets/signature.js
new file mode 100644
index 00000000000..25cb16fdb45
--- /dev/null
+++ b/addons/web/static/src/js/widgets/signature.js
@@ -0,0 +1,97 @@
+odoo.define('web.signature_widget', function (require) {
+"use strict";
+
+const framework = require('web.framework');
+const SignatureDialog = require('web.signature_dialog');
+const widgetRegistry = require('web.widget_registry');
+const Widget = require('web.Widget');
+
+
+const WidgetSignature = Widget.extend({
+ custom_events: Object.assign({}, Widget.prototype.custom_events, {
+ upload_signature: '_onUploadSignature',
+ }),
+ events: Object.assign({}, Widget.prototype.events, {
+ 'click .o_sign_label': '_onClickSignature',
+ }),
+ template: 'SignButton',
+ /**
+ * @constructor
+ * @param {Widget} parent
+ * @param {Object} record
+ * @param {Object} nodeInfo
+ */
+ init: function (parent, record, nodeInfo) {
+ this._super.apply(this, arguments);
+ this.res_id = record.res_id;
+ this.res_model = record.model;
+ this.state = record;
+ this.node = nodeInfo;
+ // signature_field is the field on which the signature image will be
+ // saved (`signature` by default).
+ this.signature_field = this.node.attrs.signature_field || 'signature';
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Open a dialog to sign.
+ *
+ * @private
+ */
+ _onClickSignature: function () {
+ const nameAndSignatureOptions = {
+ displaySignatureRatio: 3,
+ mode: 'draw',
+ noInputName: true,
+ signatureType: 'signature',
+ };
+
+ if (this.node.attrs.full_name) {
+ let signName;
+ const fieldFullName = this.state.data[this.node.attrs.full_name];
+ if (fieldFullName && fieldFullName.type === 'record') {
+ signName = fieldFullName.data.display_name;
+ } else {
+ signName = fieldFullName;
+ }
+ nameAndSignatureOptions.defaultName = signName || undefined;
+ }
+
+ nameAndSignatureOptions.defaultFont = this.node.attrs.default_font || '';
+ this.signDialog = new SignatureDialog(this, {
+ nameAndSignatureOptions: nameAndSignatureOptions,
+ });
+ this.signDialog.open();
+ },
+ /**
+ * Upload the signature image (write it on the corresponding field) and
+ * close the dialog.
+ *
+ * @returns {Promise}
+ * @private
+ */
+ _onUploadSignature: function (ev) {
+ const file = ev.data.signatureImage[1];
+ const always = () => {
+ this.trigger_up('reload');
+ framework.unblockUI();
+ };
+ framework.blockUI();
+ const rpcProm = this._rpc({
+ model: this.res_model,
+ method: 'write',
+ args: [[this.res_id], {
+ [this.signature_field]: file,
+ }],
+ });
+ rpcProm.then(always).guardedCatch(always);
+ return rpcProm;
+ },
+});
+
+widgetRegistry.add('signature', WidgetSignature);
+
+});
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 7800bc1a218..6f8696926c6 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -1563,4 +1563,10 @@
</div>
</t>
+<t t-name="SignButton">
+ <button t-attf-class="btn o_sign_button #{widget.node.attrs.highlight?'btn-primary':'btn-secondary'}">
+ <span class="o_sign_label"><t t-esc="widget.node.attrs.string"/></span>
+ </button>
+</t>
+
</templates>
diff --git a/addons/web/static/tests/fields/signature_tests.js b/addons/web/static/tests/fields/signature_tests.js
index fcc3b67074f..088c6d705cb 100644
--- a/addons/web/static/tests/fields/signature_tests.js
+++ b/addons/web/static/tests/fields/signature_tests.js
@@ -113,6 +113,105 @@ QUnit.module('signature', {
form.destroy();
});
+ QUnit.module('Signature Widget');
+
+ QUnit.test('Signature widget renders a Sign button', async function (assert) {
+ assert.expect(3);
+
+ const form = await createView({
+ View: FormView,
+ model: 'partner',
+ res_id: 1,
+ data: this.data,
+ arch: '<form>' +
+ '<header>' +
+ '<widget name="signature" string="Sign"/>' +
+ '</header>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (route === '/web/sign/get_fonts/') {
+ return Promise.resolve();
+ }
+ return this._super(route, args);
+ },
+ });
+
+ assert.containsOnce(form, 'button.o_sign_button.o_widget',
+ "Should have a signature widget button");
+ assert.strictEqual($('.modal-dialog').length, 0,
+ "Should not have any modal");
+ // Clicks on the sign button to open the sign modal.
+ await testUtils.dom.click(form.$('span.o_sign_label'));
+ assert.strictEqual($('.modal-dialog').length, 1,
+ "Should have one modal opened");
+
+ form.destroy();
+ });
+
+ QUnit.test('Signature widget: full_name option', async function (assert) {
+ assert.expect(2);
+
+ const form = await createView({
+ View: FormView,
+ model: 'partner',
+ res_id: 1,
+ data: this.data,
+ arch: '<form>' +
+ '<header>' +
+ '<widget name="signature" string="Sign" full_name="display_name"/>' +
+ '</header>' +
+ '<field name="display_name"/>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (route === '/web/sign/get_fonts/') {
+ return Promise.resolve();
+ }
+ return this._super(route, args);
+ },
+ });
+
+ // Clicks on the sign button to open the sign modal.
+ await testUtils.dom.click(form.$('span.o_sign_label'));
+ assert.strictEqual($('.modal .modal-body a.o_web_sign_auto_button').length, 1,
+ "Should open a modal with \"Auto\" button");
+ assert.strictEqual($('.modal .modal-body .o_web_sign_name_input').val(), "Pop's Chock'lit",
+ "Correct Value should be set in the input for auto drawing the signature");
+
+ form.destroy();
+ });
+
+ QUnit.test('Signature widget: highlight option', async function (assert) {
+ assert.expect(3);
+
+ const form = await createView({
+ View: FormView,
+ model: 'partner',
+ res_id: 1,
+ data: this.data,
+ arch: '<form>' +
+ '<header>' +
+ '<widget name="signature" string="Sign" highlight="1"/>' +
+ '</header>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ if (route === '/web/sign/get_fonts/') {
+ return Promise.resolve();
+ }
+ return this._super(route, args);
+ },
+ });
+
+ assert.hasClass(form.$('button.o_sign_button.o_widget'), 'btn-primary',
+ "The button must have the 'btn-primary' class as \"highlight=1\"");
+ // Clicks on the sign button to open the sign modal.
+ await testUtils.dom.click(form.$('span.o_sign_label'));
+ assert.isNotVisible($('.modal .modal-body a.o_web_sign_auto_button'),
+ "\"Auto\" button must be invisible");
+ assert.strictEqual($('.modal .modal-body .o_web_sign_name_input').val(), '',
+ "No value should be set in the input for auto drawing the signature");
+
+ form.destroy();
+ });
});
});
});
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index 297d6b63bde..294475e5aca 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -281,6 +281,7 @@
<script type="text/javascript" src="/web/static/src/js/widgets/dropdown_menu.js"></script>
<script type="text/javascript" src="/web/static/src/js/widgets/pie_chart.js"></script>
<script type="text/javascript" src="/web/static/src/js/widgets/ribbon.js"></script>
+ <script type="text/javascript" src="/web/static/src/js/widgets/signature.js"></script>
<script type="text/javascript" src="/web/static/src/js/apps.js"></script>
<script type="text/javascript" src="/web/static/src/js/fields/abstract_field.js"></script>