Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/pluggableWidgets/signature-native/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

## [2.4.0] - 2026-4-10

### Added

- Added directImage save mode that uploads the signature directly to a System.Image object

## [2.3.0] - 2025-7-7

- Updated react-native-webview from version v13.12.5 to latest
Expand Down
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/signature-native/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "signature-native",
"widgetName": "Signature",
"version": "2.3.0",
"version": "2.4.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import { StructurePreviewProps, topBar } from "@mendix/piw-utils-internal";
import { hidePropertiesIn, Properties } from "@mendix/pluggable-widgets-tools";

import { SignaturePreviewProps } from "../typings/SignatureProps";

export const getPreview = (_: SignaturePreviewProps, isDarkMode: boolean): StructurePreviewProps =>
topBar("Signature", [], isDarkMode);

export function getProperties(values: SignaturePreviewProps, defaultProperties: Properties): Properties {
if (values.saveMode === "attribute") {
hidePropertiesIn(defaultProperties, values, ["imageObject"]);
}

if (values.saveMode === "directImage") {
hidePropertiesIn(defaultProperties, values, ["imageAttribute"]);
}

return defaultProperties;
}
67 changes: 64 additions & 3 deletions packages/pluggableWidgets/signature-native/src/Signature.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { mergeNativeStyles, extractStyles } from "@mendix/pluggable-widgets-tools";
import { executeAction } from "@mendix/piw-utils-internal";
import { ReactElement, useCallback, useRef } from "react";
import RNBlobUtil from "react-native-blob-util";
import { View, Text } from "react-native";
import SignatureScreen, { SignatureViewRef } from "react-native-signature-canvas";
import { Touchable } from "./components/Touchable";
Expand All @@ -10,6 +11,47 @@ import { SignatureStyle, defaultSignatureStyle, webStyles } from "./ui/Styles";

export type Props = SignatureProps<SignatureStyle>;

declare const mx: {
data: {
saveDocument(
guid: string,
fileName: string,
params: object,
blob: Blob,
callback: () => void,
error: (error: Error) => void
): void;
};
};

function getCleanBase64(signature: string): string {
return signature.includes(",") ? signature.split(",")[1].replace(/\s/g, "") : signature.replace(/\s/g, "");
}

async function uploadSignature(signature: string, guid: string): Promise<void> {
const tempPath = `${RNBlobUtil.fs.dirs.CacheDir}/temp_signature_${Date.now()}.png`;

try {
await RNBlobUtil.fs.writeFile(tempPath, getCleanBase64(signature), "base64");

const response = await fetch(`file://${tempPath}`);
const blob = await response.blob();

return await new Promise((resolve, reject) => {
mx.data.saveDocument(
guid,
"camera image",
{},
blob,
() => resolve(),
(error: Error) => reject(error)
);
});
} finally {
RNBlobUtil.fs.unlink(tempPath).catch(() => undefined);
}
}

export function Signature(props: Props): ReactElement {
const ref = useRef<SignatureViewRef>(null);
const styles = mergeNativeStyles(defaultSignatureStyle, props.style);
Expand All @@ -28,11 +70,30 @@ export function Signature(props: Props): ReactElement {
const buttonCaptionSave = props.buttonCaptionSave?.value ?? "Save";

const handleSignature = useCallback(
(base64signature: string): void => {
props.imageAttribute.setValue(base64signature);
async (base64signature: string): Promise<void> => {
if (props.saveMode === "directImage") {
const targetGuid = props.imageObject?.value?.id;

if (!targetGuid) {
console.error(
"Signature direct image mode requires the widget to be placed inside a System.Image data container."
);
return;
}

try {
await uploadSignature(base64signature, targetGuid);
executeAction(props.onSave);
} catch (error) {
console.error("Failed to upload signature image:", error);
}
return;
}

props.imageAttribute?.setValue(base64signature);
executeAction(props.onSave);
},
[props.imageAttribute, props.onSave]
[props.imageAttribute, props.imageObject, props.onSave, props.saveMode]
);

return (
Expand Down
29 changes: 19 additions & 10 deletions packages/pluggableWidgets/signature-native/src/Signature.xml
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<widget id="com.mendix.widget.native.signature.Signature" supportedPlatform="Native" needsEntityContext="true"
offlineCapable="true" pluginWidget="true" xmlns="http://www.mendix.com/widget/1.0/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.mendix.com/widget/1.0/ ../../../../node_modules/mendix/custom_widget.xsd">
<?xml version="1.0" encoding="utf-8" ?>
<widget id="com.mendix.widget.native.signature.Signature" supportedPlatform="Native" needsEntityContext="true" offlineCapable="true" pluginWidget="true" xmlns="http://www.mendix.com/widget/1.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mendix.com/widget/1.0/ ../../../../node_modules/mendix/custom_widget.xsd">
<name>Signature</name>
<description>Display signature.</description>
<icon>
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
</icon>
<properties>
<propertyGroup caption="General">
<propertyGroup caption="General">
<property key="imageAttribute" type="attribute">
<property key="saveMode" type="enumeration" defaultValue="attribute">
<caption>Save mode</caption>
<description>Choose how the signature is saved. "Store as Base64 string" saves the signature as a base64 value to a String attribute — use the Base64DecodeToImage nanoflow action to convert and commit it to an image object. "Direct image upload" skips the base64 step entirely: the signature is uploaded directly to the linked System.Image object, so you only need to commit the object in your nanoflow.</description>
<enumerationValues>
<enumerationValue key="attribute">Store as Base64 string</enumerationValue>
<enumerationValue key="directImage">Direct image</enumerationValue>
</enumerationValues>
</property>
<property key="imageAttribute" type="attribute" required="false">
<caption>Attribute</caption>
<description/>
<description>The String attribute that receives the signature as a base64 value when save mode is Store as Base64 string.</description>
<attributeTypes>
<attributeType name="String"/>
<attributeType name="String" />
</attributeTypes>
</property>
<property key="imageObject" type="contextObject" required="false">
<caption>Target image</caption>
<description>The context System.Image object to which the signature will be uploaded when save mode is Direct image.</description>
</property>
</propertyGroup>
<propertyGroup caption="Buttons">
<property key="buttonCaptionClear" type="textTemplate" required="false">
Expand Down Expand Up @@ -49,7 +58,7 @@
</property>
</propertyGroup>
<propertyGroup caption="Common">
<systemProperty key="Name"/>
<systemProperty key="Name" />
</propertyGroup>
</properties>
</widget>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@
import { Signature, Props } from "../Signature";
import { actionValue, dynamicValue, EditableValueBuilder } from "@mendix/piw-utils-internal";

jest.mock("react-native-blob-util", () => ({
__esModule: true,
default: {
fs: {
dirs: { CacheDir: "/tmp" },
writeFile: jest.fn().mockResolvedValue(undefined),
unlink: jest.fn().mockResolvedValue(undefined)
},
fetch: jest.fn()
}
}));

jest.mock("react-native", () => {
const RN = jest.requireActual("react-native");
RN.NativeModules.RNCWebView = { isFileUploadSupported: jest.fn(() => true) };
Expand All @@ -19,9 +31,9 @@
});

jest.mock("react-native-webview", () => {
const { View } = require("react-native");

Check warning on line 34 in packages/pluggableWidgets/signature-native/src/__tests__/Signature.android.spec.tsx

View workflow job for this annotation

GitHub Actions / Unit tests

Require statement not part of import statement

const WebView = (props: any) => {

Check warning on line 36 in packages/pluggableWidgets/signature-native/src/__tests__/Signature.android.spec.tsx

View workflow job for this annotation

GitHub Actions / Unit tests

Missing return type on function
return <View {...props} testID="mockWebView" />;
};

Expand All @@ -45,7 +57,8 @@
style: [],
imageAttribute: new EditableValueBuilder<string>().withValue("").build(),
buttonCaptionClear: dynamicValue<string>("Clear"),
buttonCaptionSave: dynamicValue<string>("Save")
buttonCaptionSave: dynamicValue<string>("Save"),
saveMode: "attribute"
};

describe("Signature Android", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
import { Signature, Props } from "../Signature";
import { actionValue, dynamicValue, EditableValueBuilder } from "@mendix/piw-utils-internal";

jest.mock("react-native-blob-util", () => ({
__esModule: true,
default: {
fs: {
dirs: { CacheDir: "/tmp" },
writeFile: jest.fn().mockResolvedValue(undefined),
unlink: jest.fn().mockResolvedValue(undefined)
},
fetch: jest.fn()
}
}));

jest.mock("react-native", () => {
const RN = jest.requireActual("react-native");
RN.NativeModules.RNCWebView = { isFileUploadSupported: jest.fn(() => true) };
Expand All @@ -21,13 +33,14 @@
style: [],
imageAttribute: new EditableValueBuilder<string>().withValue("").build(),
buttonCaptionClear: dynamicValue<string>("Clear"),
buttonCaptionSave: dynamicValue<string>("Save")
buttonCaptionSave: dynamicValue<string>("Save"),
saveMode: "attribute"
};

jest.mock("react-native-webview", () => {
const { View } = require("react-native");

Check warning on line 41 in packages/pluggableWidgets/signature-native/src/__tests__/Signature.ios.spec.tsx

View workflow job for this annotation

GitHub Actions / Unit tests

Require statement not part of import statement

const WebView = (props: any) => {

Check warning on line 43 in packages/pluggableWidgets/signature-native/src/__tests__/Signature.ios.spec.tsx

View workflow job for this annotation

GitHub Actions / Unit tests

Missing return type on function
return <View {...props} testID="mockWebView" />;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://www.mendix.com/package/1.0/">
<clientModule name="Signature" version="2.3.0" xmlns="http://www.mendix.com/clientModule/1.0/">
<clientModule name="Signature" version="2.4.0" xmlns="http://www.mendix.com/clientModule/1.0/">
<widgetFiles>
<widgetFile path="Signature.xml" />
</widgetFiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
* @author Mendix Widgets Framework Team
*/
import { CSSProperties } from "react";
import { ActionValue, DynamicValue, EditableValue } from "mendix";
import { ActionValue, DynamicValue, EditableValue, ObjectItem } from "mendix";

export type SaveModeEnum = "attribute" | "directImage";

export interface SignatureProps<Style> {
name: string;
style: Style[];
imageAttribute: EditableValue<string>;
saveMode: SaveModeEnum;
imageAttribute?: EditableValue<string>;
imageObject?: DynamicValue<ObjectItem>;
buttonCaptionClear?: DynamicValue<string>;
buttonCaptionSave?: DynamicValue<string>;
onClear?: ActionValue;
Expand All @@ -29,7 +33,9 @@ export interface SignaturePreviewProps {
readOnly: boolean;
renderMode: "design" | "xray" | "structure";
translate: (text: string) => string;
saveMode: SaveModeEnum;
imageAttribute: string;
imageObject: {} | { type: string } | null;
buttonCaptionClear: string;
buttonCaptionSave: string;
onClear: {} | null;
Expand Down
Loading