> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/get-started/integrations/mendix/save.md).

# Save Annotations in Mendix

Learn how to add an annotation attribute to a custom widget in Mendix Studio. Follow the step-by-step guide and optimize your web viewer for seamless user experience. Don't miss out on this essential

After following the [getting started guide](/web/get-started/readme.md), you can add an attribute that will store the [annotation data](/web/annotation/import-export.md) that can prepopulate an attribute which can then be passed to the custom widget. The attribute will be a text field for where you want to save [annotations](/web/annotation/import-export.md). The attribute length must be unlimited.

## Adding a custom attribute

Inside of the `/CustomWidgets/WebViewer` directory, open in it your code editor and change the following files.

### WebViewer.xml

Add a new property inside of `<propertyGroup caption="General"></propertyGroup>`:

{% tabs %}
{% tab title="XML" %}
{% code lineNumbers="true" %}

```xml
<property key="annotationAttribute" type="attribute">
    <caption>Annotation</caption>
    <description/>                
    <attributeTypes>                    
        <attributeType name="String"/>                
    </attributeTypes>
</property>
```

{% endcode %}
{% endtab %}
{% endtabs %}

### WebViewer.tsx

Refactor this component to pass all props as `mendixProps`:

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
import { Component, ReactNode, createElement } from "react";
import PDFViewer from "./components/PDFViewer";
import { WebViewerContainerProps } from "../typings/WebViewerProps";
import "./ui/WebViewer.css";

export default class WebViewer extends Component<WebViewerContainerProps> {    
    render(): ReactNode {        
        return <PDFViewer mendixProps={this.props} />;    
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### WebViewer.editorPreview\.tsx

Refactor this component to pass all props as `mendixProps`:

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
import { Component, ReactNode, createElement } from "react";
import PDFViewer from "./components/PDFViewer";
import { WebViewerPreviewProps } from "../typings/WebViewerProps";

declare function require(name: string): string;

export class preview extends Component<WebViewerPreviewProps> {    
    render(): ReactNode {        
        return <PDFViewer mendixProps={this.props}/>;    
    }
}

export function getPreviewCss(): string {    
    return require("./ui/WebViewer.css");
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Components/PDFViewer.tsx

Refactor this component to handle `mendixProps` and load the URL that got passed in:

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
import { createElement, useRef, useEffect, useState } from "react";
import viewer, { Core, WebViewerInstance } from "@pdftron/webviewer";

export interface InputProps {    
    mendixProps: any
}

const PDFViewer: React.FC<InputProps> = ({ mendixProps }) => {    
    const viewerRef = useRef<HTMLDivElement>(null);    
    const [instance, setInstance] = useState<null | WebViewerInstance>(null);    
    const [annotationManager, setAnnotationManager] = useState<null |Core.AnnotationManager>(null);

    useEffect(() => {        
        viewer({                
            path: "/resources/lib",            
        }, viewerRef.current as HTMLDivElement).then(instance => {
            const { documentViewer } = instance.Core;            
            setInstance(instance);            
            setAnnotationManager(documentViewer.getAnnotationManager());        
        });    
    }, []);
    
    // load document coming from the URL attribute    
    useEffect(() => {        
        if(instance && mendixProps.urlAttribute.value !== '') {
            instance.UI.loadDocument(mendixProps.urlAttribute.value);
    }    }, [instance, mendixProps.urlAttribute]);

    // save annotation data to the annotation attribute    
    useEffect(() => {        
        const updateAnnotation = async () => {
            if (annotationManager) {
                const xfdf = await annotationManager.exportAnnotations({ links: false, widgets: false });
                mendixProps.annotationAttribute.setValue(xfdf);
            }        
        }

        if(instance && annotationManager) {            
            instance.UI.setHeaderItems(header => {                
                header.push({                  
                    type: 'actionButton',                  
                    img: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/></svg>',onClick: updateAnnotation                
                });           
            });       
        }    
    }, [instance, annotationManager]);

    return <div className="webviewer" ref={viewerRef}></div>;
};

export default PDFViewer;
```

{% endcode %}
{% endtab %}

{% tab title="JavaScript (SDK v7.0+)" %}
{% code lineNumbers="true" %}

```js
import { createElement, useRef, useEffect, useState } from "react";
import viewer, { CoreControls, WebViewerInstance } from "@pdftron/webviewer";

export interface InputProps {    
    mendixProps: any
}

const PDFViewer: React.FC<InputProps> = ({ mendixProps }) => {    
    const viewerRef = useRef<HTMLDivElement>(null);    
    const [instance, setInstance] = useState<null | WebViewerInstance>(null);    
    const [annotationManager, setAnnotationManager] = useState<null | CoreControls.AnnotationManager>(null);

    useEffect(() => {        
        viewer({                
            path: "/resources/lib",            
        }, viewerRef.current as HTMLDivElement).then(instance => {
            const { docViewer } = instance;            
            setInstance(instance);            
            setAnnotationManager(docViewer.getAnnotationManager());        
        });    
    }, []);
    
    // load document coming from the URL attribute    
    useEffect(() => {        
        if(instance && mendixProps.urlAttribute.value !== '') {
            instance.loadDocument(mendixProps.urlAttribute.value);
    }    }, [instance, mendixProps.urlAttribute]);

    // save annotation data to the annotation attribute    
    useEffect(() => {        
        const updateAnnotation = async () => {
            if (annotationManager) {
                const xfdf = await annotationManager.exportAnnotations({ links: false, widgets: false });
                mendixProps.annotationAttribute.setValue(xfdf);
            }        
        }

        if(instance && annotationManager) {            
            instance.setHeaderItems(header => {                
                header.push({                  
                    type: 'actionButton',                  
                    img: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/></svg>',onClick: updateAnnotation                
                });           
            });       
        }    
    }, [instance, annotationManager]);

    return <div className="webviewer" ref={viewerRef}></div>;
};

export default PDFViewer;
```

{% endcode %}
{% endtab %}
{% endtabs %}

The code snippet above adds a new save button that saves to an annotation attribute passed to the custom widget. Do not forget to run `npm run dev` on the `CustomWidgets/WebViewer` directory in your terminal to build it and in Mendix Studio run the command to synchronize project directory under `Project/Synchronise Project Directory` or hitting `F4`. After that run the project and try it out.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.apryse.com/web/get-started/integrations/mendix/save.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
