Fetch XML of current action performed only

Product:

Product Version:“pdftron/webviewer”: “^10.4.0”

Brief summary of your issue:
Creating a project on react js
issue is i want to apply custom user selection in which i have to open a modal for user selection all user i have select its shown in my custom drop down
but know i wan to apply every form changes like add remove update delete should be add into a custom json
etc
const json = {
user1: “”,
user2: “”,
// Add more users as needed
};
like if i select user one so every form action i have performed its save its XFDF into user1 and when i have changes user to user2 it should apply after that action of form perform save its XFDF in to user2 Jason if i switch to user1 its import XFDF of user 1 if previously XFDF exists init and again start update it without removing the previously data added into a user1 and start to add more XFDF data init
how can i achive this

Please describe your issue and provide steps to reproduce it:
currently i am tring to achive this goal using addEventListener annotationChanged
but on using exportAnnotCommand() its generate error

TypeError: annotationManager.exportAnnotCommand is not a function

or on using annotationManager.exportAnnotations()
its give me whole XFDF which i didnt want

Please provide a link to a minimal sample where the issue is reproducible:


  const updateXFDF = (action, xfdf, userSelect) => {
    setUserAnnotations((prevState) => {
      // Check if the userSelect already has some data
      if (prevState[userSelect]) {
        // If userSelect already has data, concatenate the new xfdf data to it
        let newXfdf = prevState[userSelect] + xfdf;

        // Split the concatenated string by the annotation start tag "<"
        // and filter out duplicates
        newXfdf = newXfdf
          .split("<")
          .filter((value, index, self) => {
            return self.indexOf(value) === index;
          })
          .join("<");

        return {
          ...prevState,
          [userSelect]: newXfdf,
        };
      } else {
        // If userSelect does not have any data, simply set the xfdf as its value
        return {
          ...prevState,
          [userSelect]: xfdf,
        };
      }
    });
  };
  // if using a class, equivalent of componentDidMount
  useEffect(() => {
    if (webViewer.attachmentBlob) {
      WebViewer(
        {
          path: "/webviewer/lib",
          showLocalFilePicker: true,
          fullAPI: true,
          licenseKey:
            "1693909073058:7c3***", // sign up to get a free trial key at https://dev.apryse.com
        },

        viewer.current
      ).then(async (instance) => {
        instance.UI.loadDocument(base64ToBlob(webViewer.attachmentBlob), {
          filename: fileName,
        });
        const { documentViewer, annotationManager } = instance.Core;
        let userSelect = selectedUser;
        //======================================== disable header =====================================//
        instance.UI.disableElements([
          "outlinesPanelButton",
          "comboBoxFieldToolGroupButton",
          "listBoxFieldToolGroupButton",
          "toolsOverlay",
          "toolbarGroup-Shapes",
          "toolbarGroup-Edit",
          "toolbarGroup-Insert",
          "shapeToolGroupButton",
          "menuButton",
          "freeHandHighlightToolGroupButton",
          "underlineToolGroupButton",
          "freeHandToolGroupButton",
          "stickyToolGroupButton",
          "squigglyToolGroupButton",
          "strikeoutToolGroupButton",
          "notesPanel",
          "viewControlsButton",
          "selectToolButton",
          "toggleNotesButton",
          "searchButton",
          "freeTextToolGroupButton",
          "crossStampToolButton",
          "checkStampToolButton",
          "dotStampToolButton",
          "rubberStampToolGroupButton",
          "dateFreeTextToolButton",
          "eraserToolButton",
          "panToolButton",
          "signatureToolGroupButton",
          "viewControlsOverlay",
          "contextMenuPopup",
        ]);
        //======================================== disable header =====================================//

        //======================================== for cutome side bar =====================================//

        // Example handler for when the dropdown value changes
        const handleDropdownChange = (selectedValue) => {
          setSelectedUser(selectedValue);
          userSelect = selectedValue;
        };

        const openCustomModal = () => {
          setOpenAddParticipentModal(true); // Open the custom modal
        };
        // Create a render function for the custom panel
        const renderCustomPanel = () => {
          return (
            <div>
              <label htmlFor="participantDropdown">{t("Participant")}</label>
              <select
                id="participantDropdown"
                name="Participant"
                onChange={(e) => handleDropdownChange(e.target.value)}
              >
                <option value="user1">user1</option>
                <option value="user2">user2</option>
                {/* Add more options as needed */}
              </select>
              <button onClick={openCustomModal}>Open Custom Modal</button>
            </div>
          );
        };
        var myCustomPanel = {
          tab: {
            dataElement: "customPanelTab",
            title: "customPanelTab",
            img: "/favicon-32x32.png",
          },
          panel: {
            dataElement: "customPanel",
            render: renderCustomPanel,
          },
        };
        instance.UI.setCustomPanel(myCustomPanel);
        //======================================== for cutome side bar =====================================//

        //======================================== header save button =====================================//
        async function generateBase64FromBlob(blob) {
          return new Promise((resolve, reject) => {
            const reader = new FileReader();

            reader.onload = function () {
              const base64String = reader.result.split(",")[1];
              resolve(base64String);
            };

            reader.onerror = function (error) {
              reject(error);
            };

            reader.readAsDataURL(blob);
          });
        }
        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: async () => {
              // save the annotations
              const xfdfString = await annotationManager.exportAnnotations({
                widgets: false,
                links: false,
                //fields: false
              });
              console.log("xfdfStringxfdf String", xfdfString);
              const doc = documentViewer.getDocument();
              const data = await doc.getFileData({
                // saves the document with annotations in it
                xfdfString,
              });
              const arr = new Uint8Array(data);
              const blob = new Blob([arr], { type: "application/pdf" });
              generateBase64FromBlob(blob)
                .then((base64String) => {
                  console.log("xfdfStringxfdf Base64 String:", base64String);
                })
                .catch((error) => {
                  console.error("Error generating base64 string:", error);
                });
              // Dispatch your Redux action to send the data to the API
              // if (Number(commingFrom) === 1) {
              //   const apiData = {
              //     TaskID: taskId, // Assuming taskId is defined in your component
              //     TaskAttachementID: attachmentID, // Assuming attachmentID is defined in your component
              //     AnnotationString: xfdfString, // Pass the annotations data here
              //   };
              //   // for todo
              //   dispatch(addAnnotationsOnToDoAttachement(navigate, t, apiData));
              // } else if (Number(commingFrom) === 2) {
              //   let notesData = {
              //     NoteID: taskId,
              //     NoteAttachementID: attachmentID,
              //     AnnotationString: xfdfString,
              //   };
              //   dispatch(
              //     addAnnotationsOnNotesAttachement(navigate, t, notesData)
              //   );
              //   // for notes
              // } else if (Number(commingFrom) === 3) {
              //   let resolutionData = {
              //     ResolutionID: taskId,
              //     ResolutionAttachementID: attachmentID,
              //     AnnotationString: xfdfString,
              //   };
              //   dispatch(
              //     addAnnotationsOnResolutionAttachement(
              //       navigate,
              //       t,
              //       resolutionData
              //     )
              //   );
              //   // for resultion
              // } else if (Number(commingFrom) === 4) {
              //   // for data room
              //   let dataRoomData = {
              //     FileID: attachmentID,
              //     AnnotationString: xfdfString,
              //   };

              //   dispatch(
              //     addAnnotationsOnDataroomAttachement(navigate, t, dataRoomData)
              //   );
              // }
            },
          });

          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: async () => {},
          });
        });
        //======================================== header save button =====================================//

        //======================================== for documentLoaded =====================================//
        await documentViewer.getAnnotationsLoadedPromise();
        documentViewer.addEventListener("documentLoaded", async () => {
          // annotManager.setCurrentUser(name);
          if (webViewer.xfdfData !== "" && annotationManager) {
            try {
              await annotationManager.importAnnotations(webViewer.xfdfData);
            } catch (error) {
              console.log("importAnnotations", error);
            }
          }
        });
        //======================================== for documentLoaded =====================================//

        //======================================== for every single line anotation =====================================//

        annotationManager.addEventListener(
          "annotationChanged",
          async (annotations, action, { imported }) => {
            if (imported) {
              return;
            }
            try {
              // Export annotations to XFDF format using `exportAnnotations`
              const xfdf = await annotationManager.exportAnnotations();

              // Update the user's annotations based on the action
              updateXFDF(action, xfdf, userSelect);
            } catch (error) {
              console.error("Error in annotationChanged event:", error);
            }
          }
        );
        //======================================== for every single line anotation =====================================//
      });
    }
  }, [webViewer.attachmentBlob]);

currently i have set

1 Like

Thank you for posting the incident to our forum. We will provide you with an update as soon as possible.

1 Like

Hello huzeifajahangir,

The error: annotationManager.exportAnnotCommand is not a funcion is true, as it is now:
annotationManager.exportAnnotationCommand() in the latest versions of WebViewer

I may be misunderstanding the usecase, but you can also do:
annotationManager.exportAnnotations({annotationList: [a list of annotations which you want to export]})
so it does not export the entire annotation list.

Let me know if this works for you,

Best regards,
Tyler

1 Like

so how can i get annotation xml of the current action only
and header ,footer xml separately export

1 Like

Hello huzeifajahangir,

If youre using the exportAnnotationCommand API you can do:

const parser = new DOMParser();
const data = parser.parseFromString(commandXFDF, 'text/xml');

console.log(data.children[0].children)

Will return this:

Best regards,
Tyler

Thank you very much for the help, it finally worked for me.