Add a custom "Save" button

This sample shows how to add a custom "Save" button to the main viewer toolbar that saves the edited PDF on the server using SupportApi.

app.js
index.html
styles.css
SampleSupportController.cs
window.onload = function () { // DsPdfViewer.LicenseKey = "***key***"; let viewer, React; // Configure viewer options const viewerOptions = { workerSrc: "/document-solutions/javascript-pdf-viewer/demos/product-bundles/build/dspdfviewer.worker.js", supportApi: getSupportApiSettings(), userData: { sampleName: "SaveChangesSample", docName: `${Date.now()}.pdf` } }; // Initialize the PDF viewer instance viewer = new DsPdfViewer("#viewer", viewerOptions); // Add the annotation editor panel viewer.addAnnotationEditorPanel(); // Retrieve the React library used by the viewer React = viewer.getType("React"); // Create a custom 'Save Changes' button viewer.toolbar.addItem({ key: "custom-save", icon: { type: "svg", content: React.createElement( "svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "#ff0000" }, React.createElement("path", { d: "M20.913 9.058c0.057-0.26 0.087-0.531 0.087-0.808 0-2.071-1.679-3.75-3.75-3.75-0.333 0-0.657 0.044-0.964 0.125-0.581-1.813-2.28-3.125-4.286-3.125-2.047 0-3.775 1.367-4.32 3.238-0.533-0.155-1.097-0.238-1.68-0.238-3.314 0-6 2.686-6 6s2.686 6 6 6h3v4.5h6v-4.5h5.25c2.071 0 3.75-1.679 3.75-3.75 0-1.845-1.333-3.379-3.087-3.692zM13.5 15v4.5h-3v-4.5h-3.75l5.25-5.25 5.25 5.25h-3.75z" }) ) }, title: "Save Document", enabled: false, action: function () { // Save changes to the server viewer.saveChanges().then(function (success) { if (success) { const documentUrl = `${window.top.SUPPORTAPI_URL}/GetPdfFromCloud?docName=${viewerOptions.userData.docName}&clientId=${viewer.supportApi.clientId}`; viewer.open(documentUrl).then(function () { alert("The document has been saved in the cloud. It will be available for 10 minutes."); }); } }); }, onUpdate: function () { return { enabled: viewer.hasDocument, title: "Save Changes" }; } }); // Add default 'Download' and custom 'Save' buttons to the viewer toolbar viewer.toolbarLayout.viewer.default.unshift("download", "custom-save"); viewer.toolbarLayout.viewer.mobile.unshift("download", "custom-save"); // Configure the annotation editor toolbar layout const annotationEditorToolbarItems = [ "custom-save", "download", "edit-select", "edit-sign-tool", "$split", "edit-text", "edit-free-text", "edit-ink", "edit-square", "edit-circle", "edit-line", "edit-polyline", "edit-polygon", "edit-stamp", "edit-link", "$split", "edit-redact", "edit-redact-apply", "edit-erase", "$split", "new-document", "$split", "new-page", "delete-page" ]; viewer.toolbarLayout.annotationEditor = { default: annotationEditorToolbarItems, mobile: annotationEditorToolbarItems, fullscreen: annotationEditorToolbarItems }; // Open the initial document viewer.open("/document-solutions/javascript-pdf-viewer/demos/product-bundles/assets/pdf/viewer-save-changes.pdf"); };
window.onload = function () { // DsPdfViewer.LicenseKey = "***key***"; let viewer, React; // Configure viewer options const viewerOptions = { workerSrc: "/document-solutions/javascript-pdf-viewer/demos/product-bundles/build/dspdfviewer.worker.js", supportApi: getSupportApiSettings(), userData: { sampleName: "SaveChangesSample", docName: `${Date.now()}.pdf` } }; // Initialize the PDF viewer instance viewer = new DsPdfViewer("#viewer", viewerOptions); // Add the annotation editor panel viewer.addAnnotationEditorPanel(); // Retrieve the React library used by the viewer React = viewer.getType("React"); // Create a custom 'Save Changes' button viewer.toolbar.addItem({ key: "custom-save", icon: { type: "svg", content: React.createElement( "svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "#ff0000" }, React.createElement("path", { d: "M20.913 9.058c0.057-0.26 0.087-0.531 0.087-0.808 0-2.071-1.679-3.75-3.75-3.75-0.333 0-0.657 0.044-0.964 0.125-0.581-1.813-2.28-3.125-4.286-3.125-2.047 0-3.775 1.367-4.32 3.238-0.533-0.155-1.097-0.238-1.68-0.238-3.314 0-6 2.686-6 6s2.686 6 6 6h3v4.5h6v-4.5h5.25c2.071 0 3.75-1.679 3.75-3.75 0-1.845-1.333-3.379-3.087-3.692zM13.5 15v4.5h-3v-4.5h-3.75l5.25-5.25 5.25 5.25h-3.75z" }) ) }, title: "Save Document", enabled: false, action: function () { // Save changes to the server viewer.saveChanges().then(function (success) { if (success) { const documentUrl = `${window.top.SUPPORTAPI_URL}/GetPdfFromCloud?docName=${viewerOptions.userData.docName}&clientId=${viewer.supportApi.clientId}`; viewer.open(documentUrl).then(function () { alert("The document has been saved in the cloud. It will be available for 10 minutes."); }); } }); }, onUpdate: function () { return { enabled: viewer.hasDocument, title: "Save Changes" }; } }); // Add default 'Download' and custom 'Save' buttons to the viewer toolbar viewer.toolbarLayout.viewer.default.unshift("download", "custom-save"); viewer.toolbarLayout.viewer.mobile.unshift("download", "custom-save"); // Configure the annotation editor toolbar layout const annotationEditorToolbarItems = [ "custom-save", "download", "edit-select", "edit-sign-tool", "$split", "edit-text", "edit-free-text", "edit-ink", "edit-square", "edit-circle", "edit-line", "edit-polyline", "edit-polygon", "edit-stamp", "edit-link", "$split", "edit-redact", "edit-redact-apply", "edit-erase", "$split", "new-document", "$split", "new-page", "delete-page" ]; viewer.toolbarLayout.annotationEditor = { default: annotationEditorToolbarItems, mobile: annotationEditorToolbarItems, fullscreen: annotationEditorToolbarItems }; // Open the initial document viewer.open("/document-solutions/javascript-pdf-viewer/demos/product-bundles/assets/pdf/viewer-save-changes.pdf"); };
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Custom save button</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="./src/styles.css"> <script src="/document-solutions/javascript-pdf-viewer/demos/product-bundles/build/dspdfviewer.js"></script> <script src="/document-solutions/javascript-pdf-viewer/demos/product-bundles/build/wasmSupportApi.js"></script> <script src="/document-solutions/javascript-pdf-viewer/demos/resource/js/init.js"></script> <script src="./src/app.js"></script> </head> <body> <div id="viewer"></div> </body> </html>
#viewer { height: 100%; }
// This sample demonstrates how to handle PDF document modifications in a web API. // It includes loading a document, applying final changes, flattening annotations, // and saving the updated file back to storage. // The example uses disk-based storage but can be adapted for cloud services. using System; using System.IO; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using GrapeCity.Documents.Pdf; using GrapeCity.Documents.Pdf.Annotations; using GrapeCity.Documents.Pdf.ViewerSupportApi.Controllers; using GrapeCity.Documents.Pdf.ViewerSupportApi.Models; using Newtonsoft.Json.Linq; namespace GcPdfViewerSupportApiDemo.Controllers { [Route("api/pdf-viewer")] [ApiController] public class SampleSupportController : GcPdfViewerController { static SampleSupportController() { Settings.DocumentModified += OnDocumentModified; } /// <summary> /// Handles document modification event. /// </summary> private static void OnDocumentModified(object sender, DocumentModifiedEventArgs e) { var document = e.Document; ApplyFinalChanges(document); FlattenAnnotations(document); var userData = e.DocumentLoader.Info.documentOptions.userData as JObject; var docName = userData?["docName"]?.ToString() ?? "default.pdf"; SaveDocumentToCloud(e.DocumentLoader.ClientId, document, docName); } #region ** ViewerSaveChanges Sample /// <summary> /// Retrieves a PDF document from cloud storage. /// </summary> [HttpGet("GetPdfFromCloud")] [ApiExplorerSettings(IgnoreApi = true)] public async Task<IActionResult> GetPdfFromCloud(string docName, string clientId) { var fileBytes = await GetDocumentFromCloud(docName, clientId); if (fileBytes == null) return NotFound($"Document '{docName}' not found."); return File(fileBytes, "application/pdf", docName); } /// <summary> /// Saves the modified PDF document to cloud storage. /// </summary> private static void SaveDocumentToCloud(string clientId, GcPdfDocument document, string docName) { SaveToDisk(document, docName); } /// <summary> /// Downloads a PDF document from cloud storage. /// </summary> public static async Task<byte[]> GetDocumentFromCloud(string docName, string clientId) { return LoadFromDisk(docName); } #endregion #region ** PDF Processing Helpers /// <summary> /// Applies final modifications before saving the document. /// </summary> private static void ApplyFinalChanges(GcPdfDocument document) { document.Metadata.ModifyDate = DateTime.UtcNow; } /// <summary> /// Flattens annotations, converting them into static PDF content. /// </summary> private static void FlattenAnnotations(GcPdfDocument document) { var annotations = document.Pages.SelectMany(page => page.Annotations).ToList(); ConvertAnnotationsToContent(annotations); } /// <summary> /// Converts annotations into page content and removes them. /// </summary> private static void ConvertAnnotationsToContent(List<AnnotationBase> annotations) { foreach (var group in annotations.GroupBy(a => a.Page)) { var page = group.Key; if (page == null) continue; var graphics = page.Graphics; var size = page.GetRenderSize(graphics.Resolution, graphics.Resolution); var destinationRectangle = new RectangleF(0, 0, size.Width, size.Height); page.DrawAnnotations(graphics, destinationRectangle, group.ToList()); foreach (var annotation in group) { page.Annotations.Remove(annotation); } } } #endregion #region ** Disk Storage Example /// <summary> /// Loads a PDF document from disk storage. /// </summary> /// <param name="docName">The name of the document.</param> /// <returns>The file bytes if found; otherwise, throws an exception.</returns> /// <exception cref="FileNotFoundException">Thrown when the file is not found.</exception> private static byte[] LoadFromDisk(string docName) { string path = Path.Combine("Documents", docName); if (!System.IO.File.Exists(path)) throw new FileNotFoundException($"File '{docName}' not found in storage.", path); return System.IO.File.ReadAllBytes(path); } /// <summary> /// Saves a PDF document to disk storage. /// </summary> private static void SaveToDisk(GcPdfDocument document, string docName) { string directory = "Documents"; if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); string path = Path.Combine(directory, docName); document.Save(path); } #endregion } }