OWL
forms
Signature Field
Odoo 19 OWL component — Signature Field (views)
Live preview
Interactive
Source excerpt
web/static/src/views/fields/signature/signature_field.js
import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
import { SignatureDialog } from "@web/core/signature/signature_dialog";
import { useService } from "@web/core/utils/hooks";
import { imageUrl } from "@web/core/utils/urls";
import { isBinarySize } from "@web/core/utils/binary";
import { fileTypeMagicWordMap } from "@web/views/fields/image/image_field";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
import { Component, useState } from "@odoo/owl";
const placeholder = "/web/static/img/placeholder.png";
export class SignatureField extends Component {
static template = "web.SignatureField";
static props = {
...standardFieldProps,
defaultFont: { type: String },
fullName: { type: String, optional: true },
height: { type: Number, optional: true },
previewImage: { type: String, optional: true },
width: { type: Number, optional: true },
type: { validate: (t) => ["initial", "signature"].includes(t), optional: true },
};
static defaultProps = {
type: "signature",
};
setup() {
this.displaySignatureRatio = 3;
this.notification = useService("notification");
this.dialogService = useService("dialog");
this.state = useState({
isValid: true,
});
}
get rawCacheKey() {
return this.props.record.data.write_date;
}
get getUrl() {
const { name, previewImage, record } = this.props;
if (this.state.isValid && this.value) {
if (isBinarySize(this.value)) {
return imageUrl(record.resModel, record.resId, previewImage || name, {
unique: this.rawCacheKey,
});
} else {
// Use magic-word technique for detecting image type
const magic = fileTypeMagicWordMap[this.value[0]] || "png";
return `data:image/${magic};base64,${this.props.record.data[this.props.name]}`;
}
}
return placeholder;
}
get sizeStyle() {
let { width, height } = this.props;
if (!this.value) {
if (width && height) {
width = Math.min(width, this.displaySignatureRatio * height);
height = width / this.displaySignatureRatio;
} else if (width) {
height = width / this.displaySignatureRatio;
} else if (height) {
width = height * this.displaySignatureRatio;
}
}
let style = "";
if (width) {
style += `width:${width}px; max-width:${width}px;`;
}
if (height) {
style += `height:${height}px; max-height:${height}px;`;
}
return style;
}
get value() {
return this.props.record.data[this.props.name];
}
onClickSignature() {
if (!this.props.readonly) {
const nameAndSignatureProps = {
displaySignatureRatio: 3,
signatureType: this.props.type,
noInputName: true,
};
const { fullName, record } = this.props;
let defaultName = "";
if (fullName) {
let signName;
const fullNameData = record.data[fullName];
if (record.fields[fullName].type === "many2one") {
// If m2o is empty, it will have falsy value in recordData
signName = fullNameData && fullNameData.display_name;
} else {
signName = fullNameData;
}
defaultName = signName === "" ? undefined : signName;
}
nameAndSignatureProps.defaultFont = this.props.defaultFont;
const dialogProps = {
defaultName,
nameAndSignatureProps,
uploadSignature: (signature) => this.uploadSignature(signature),
};
this.dialogService.add(SignatureDialog, dialogProps);
}
}
onLoadFailed() {
this.state.isValid = false;
this.notification.add(_t("Could not display the selected image"), {
Registry / API
- Registry name
SignatureField- Category
—- Module
web- Slug
signature-field- Nav group
forms