Tech tutorials Exporting an SVG Image to a SharePoint Document Library via JavaScript
By Insight Editor / 31 Dec 2014 , Updated on 16 May 2019
By Insight Editor / 31 Dec 2014 , Updated on 16 May 2019
When creating visualizations of a process workflow into a Scalable Vector Graphics (SVG) image, one of the project requirements included saving this visualization as an image to a SharePoint document library. This article will discuss and demonstrate how to export an SVG image in a SharePoint 2013 app to a SharePoint document library in the host web.
In our project, we had significant additional code to generate the SVG image based on data in the application. For the purposes of this article, I created a simple SVG image using the Google SVG-edit tool. Details on the project with source code can be found at https://code.google.com/p/svg-edit/.
Start with the basic App for SharePoint project template in Visual Studio 2013. There are several tags added to the main content area. The first is the svgWrapper div. This div contains the SVG image code (either the code from the Google SVG-edit tool or the custom-generated SVG).
Next is a button that will call our JavaScript and export the image. Finally is a canvas (with an ID of exportCanvas), which is hidden. This will be used during the conversion process. The basic code with my sample SVG image looks like this:
<div id="svgWrapper">
<svg id="svgImage" width="640" height="480" xmlns="http://www.w3.org/2000/svg">
<g>
<title>Layer 1</title>
<rect id="svg_1" height="43" width="86" y="57" x="121" stroke-width="5" stroke="#000000" fill="#FF0000"/>
<ellipse ry="57" rx="87.5" id="svg_2" cy="100" cx="332.5" stroke-width="5" stroke="#000000" fill="#FF0000"/>
<path id="svg_3" d="m29,171c-14,26 99,22 99,22c0,0 -4,41 -8,43c-4,2 -39,51 -39,51c0,0 -62,-58 -62,-59c0,-1 10,-57 10,-57z" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="5" stroke="#000000" fill="#FF0000"/>
<path id="svg_4" d="m292.42999,252.03l0,-31.6875l0,0c0,-1.3461 1.0914,-2.4375 2.43869,-2.4375l29.25,0c1.3461,0 2.43631,1.0914 2.43631,2.4375c0,1.34669 -1.09021,2.4375 -2.43631,2.4375l-2.43869,0l0,31.6875c0,1.34669 -1.0914,2.4375 -2.4375,2.4375l-29.25,0l0,0c-1.3461,0 -2.4375,-1.09081 -2.4375,-2.4375c0,-1.3461 1.0914,-2.4375 2.4375,-2.4375l2.4375,0zm4.875,-34.125l0,0c1.3461,0 2.4375,1.0914 2.4375,2.4375c0,1.34669 -1.0914,2.4375 -2.4375,2.4375c-0.67245,0 -1.21875,-0.5457 -1.21875,-1.21875c0,-0.67305 0.5463,-1.21875 1.21875,-1.21875l2.4375,0m24.37619,2.4375l-26.81369,0m-4.875,29.25l0,0c0.67365,0 1.21875,0.5457 1.21875,1.21875c0,0.67305 -0.5451,1.21875 -1.21875,1.21875l2.43869,0m-2.43869,2.4375l0,0c1.3461,0 2.43869,-1.09081 2.43869,-2.4375l0,-2.4375" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="5" stroke="#000000" fill="#FF0000"/>
<text xml:space="preserve" text-anchor="start" id="svg_5" y="146" x="110" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="0" stroke="#000000" fill="#FF0000">
<tspan x="110" dy="0" font-weight="normal" font-style="normal" font-size="24" font-family="DroidSerif" fill="#000000" id="svg_25" xml:space="preserve">Test SVG</tspan>
</text>
</g>
</svg>
</div>
<button type="button" onclick="saveImage();">Export to Doc Lib</button>
<canvas id="exportCanvas" style="display:none;"></canvas>
Within the solution, we’ll use multiple third-party JavaScript libraries to simplify our development.
First, we start with the canvg library. This library allows us to parse the SVG image and render it onto the Canvas. To use, download the canvg.js, stackblur.js and rgbcolor.js files from https://github.com/gabelerner/canvg.
Next, we create a JavaScript file called Base64ToArrayBuffer.js based on the code under solution #2 – rewriting atob() and btoa() using TypedArrays and UTF-8 on the Mozilla Developer Network at https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding. This library helps us convert the canvas into the binary data we need to pass to SharePoint.
Finally, we add a reference to the SharePoint cross-domain library (SP.RequestExecutor.js). This will allow us to connect to the host web to save the image. The reference should look as follows:
<script type="text/javascript" src="/_layouts/15/SP.RequestExecutor.js"></script>
To start, we clone the svgWrapper div (the div that contains the SVG image) using jQuery. This allows us to manipulate the code without affecting the display of the SVG.
var svg = $("#svgWrapper").clone();
Next, we take the inner HTML of the div. This gives us the code for the SVG. (This is the same as what would be produced by the Google SVG-edit tool.)
svg = svg[0].innerHTML;
After we have the SVG code, we need to manipulate it slightly to make it into a format that canvg can parse. The first line replaces all new line characters, and the second trims the SVG code (canvg seems to have issues if the <svg> tag isn’t at the beginning of the string).
svg = svg.replace(/\n/g, '');
svg = svg.trim();
Note that if you’re using an SVG image generated by the Raphael JavaScript library, you’ll need to add another line to clear up the multiple entries of the xmlns attribute in the SVG. This can be done using the following code:
svg = svg.replace("xmlns=\"http://www.w3.org/2000/svg\" ", " ");
Finally, we’ll call the canvg library and import the SVG data into the hidden canvas tag on our page.
canvg(document.getElementById('exportCanvas'), svg, { ignoreMouse: true, ignoreAnimation: true });
To convert the image in the canvas tag to binary, we create a new JavaScript function using the functions within the Base64ToArrayBuffer.js file we created earlier. This function converts the base 64 code in the canvas to a DataURL, and then converts that DataURL to an array buffer using the base64DecToArr function.
We then use the Uint8Array to convert the array buffer into bytes. Finally, we loop through the bytes and convert the character codes into string format.
function toBinary(canvas) {
var ab = base64DecToArr(canvas.toDataURL().replace('data:image/png;base64,', ""));
var binary = '';
var bytes = new Uint8Array(ab);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return binary;
}
We can now call the toBinary function from our code to generate the image data we’ll need to post to SharePoint.
var binaryImage = toBinary(canvas);
Note that in this sample, we’re saving the image to the documents library in the host web and not a documents library in the app web. The permissions of the SharePoint app must be set to allow write permissions to the documents library. We create a new function called createImage, which we’ll use to create the image in SharePoint. This function has the following signature:
function createImage(listTitle, listPath, fileName, fileContents)
We start the process of creating the image by retrieving the host web URL and app web URL. This gets both values from the query string values passed from the host web to the SharePoint app.
var hostweburl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));
var appweburl = decodeURIComponent(getQueryStringParameter("SPAppWebUrl"));
The getQueryStringParameter function (which is readily available in multiple places on the internet) has the following definition:
function getQueryStringParameter(paramToRetrieve) {
var params =
document.URL.split("?")[1].split("&");
for (var i = 0; i < params.length; i = i + 1) {
var singleParam = params[i].split("=");
if (singleParam[0] == paramToRetrieve)
return singleParam[1];
}
}
Next, we set the URL we’ll use to post the image into the documents library. Note that the paths are different based on whether the document will be created in the root folder or a subfolder of the documents library (if the listPath variable is an empty string, the image will be in the root folder).
var addUrl;
if (listPath == "") {
addUrl = appweburl + "/_api/SP.AppContextSite(@target)/web/lists/getByTitle('" + listTitle + "')/RootFolder/Files/Add(url='" + fileName + "', overwrite=true)?@target='" + hostweburl + "'";
}
else {
addUrl = appweburl + "/_api/SP.AppContextSite(@target)/web/getfolderbyserverrelativeurl('" + listTitle + "/" + listPath + "')/Files/Add(url='" + fileName + "', overwrite=true)?@target='" + hostweburl + "'";
}
Because we want to keep our application responsive and prevent locking of the UI while the REST call is being made to SharePoint, we use the Deferred object as our method of implementing promises in our application. To do this, we add:
var deferred = $.Deferred();
at the beginning of the createImage function. We then add:
return deferred;
at the end of the function. We use deferred.resolve() to complete and resolve a successful call to SharePoint and deferred.reject() to reject a failed call. We use the SP.RequestExecutor to set up the call to SharePoint and the executeAsync method to execute the request asynchronously.
The method parameter must be set to “POST” as we’re sending the binary data for the image to SharePoint. Also, the binaryStringRequestBody must be set to true in order to specify that the image data is a binary string. The code to make the request to SharePoint looks like the following:
function createImage(listTitle, listPath, fileName, fileContents) {
var deferred = $.Deferred();
var hostweburl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));
var appweburl = decodeURIComponent(getQueryStringParameter("SPAppWebUrl"));
var addUrl;
if (listPath == "") {
addUrl = appweburl + "/_api/SP.AppContextSite(@target)/web/lists/getByTitle('" + listTitle + "')/RootFolder/Files/Add(url='" + fileName + "', overwrite=true)?@target='" + hostweburl + "'";
}
else {
addUrl = appweburl + "/_api/SP.AppContextSite(@target)/web/getfolderbyserverrelativeurl('" + listTitle + "/" + listPath + "')/Files/Add(url='" + fileName + "', overwrite=true)?@target='" + hostweburl + "'";
}
var executor = new SP.RequestExecutor(appweburl);
executor.executeAsync({
url: addUrl,
method: "POST",
headers: {
"Accept": "application/json; odata=verbose"
},
contentType: "application/json;odata=verbose",
binaryStringRequestBody: true,
body: fileContents,
success: function (data, textStatus, xhr) {
deferred.resolve(fileName);
},
error: function (xhr, textStats, errorThrown) {
deferred.reject(JSON.stringify(xhr));
}
});
Now, in order to call this function, we use the following code.
var imgTitle = "exportedSVG.png";
$.when(createImage("Documents", "", imgTitle, binaryImage)).done(function (result) {
alert('Export Image ' + result + ' Completed!');
}).fail(function (err) {
alert('Error: ' + JSON.stringify(err));
});
Note the use of the when, done and fail statements to handle the deferred object. If the request to SharePoint is successful, the done path will be followed, whereas if the request fails, the fail path will be followed.
This article has shown how to save an SVG image within a SharePoint app to a document library in the host web. Here’s the final code for the JavaScript app.js file:
'use strict';
function saveImage() {
//Clone the SVG
var svg = $("#svgWrapper").clone();
svg = svg[0].innerHTML;
svg = svg.replace("xmlns=\"http://www.w3.org/2000/svg\" ", " ");
//Needed to replace return characters
svg = svg.replace(/\n/g, '');
//The <svg> tag must be the first item in the code for canvg
svg = svg.trim();
canvg(document.getElementById('exportCanvas'), svg, { ignoreMouse: true, ignoreAnimation: true });
var canvas = document.getElementById('exportCanvas');
var binaryImage = toBinary(canvas);
var imgTitle = "exportedSVG.png";
$.when(createImage("Documents", "", imgTitle, binaryImage)).done(function (result) {
alert('Export Image ' + result + ' Completed!');
}).fail(function (err) {
alert('Error: ' + JSON.stringify(err));
});
}
function toBinary(canvas) {
var ab = base64DecToArr(canvas.toDataURL().replace('data:image/png;base64,', ""));
var binary = '';
var bytes = new Uint8Array(ab);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return binary;
}
function createImage(listTitle, listPath, fileName, fileContents) {
var deferred = $.Deferred();
var hostweburl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));
var appweburl = decodeURIComponent(getQueryStringParameter("SPAppWebUrl"));
var addUrl;
if (listPath == "") {
addUrl = appweburl + "/_api/SP.AppContextSite(@target)/web/lists/getByTitle('" + listTitle + "')/RootFolder/Files/Add(url='" + fileName + "', overwrite=true)?@target='" + hostweburl + "'";
}
else {
addUrl = appweburl + "/_api/SP.AppContextSite(@target)/web/getfolderbyserverrelativeurl('" + listTitle + "/" + listPath + "')/Files/Add(url='" + fileName + "', overwrite=true)?@target='" + hostweburl + "'";
}
var executor = new SP.RequestExecutor(appweburl);
executor.executeAsync({
url: addUrl,
method: "POST",
headers: {
"Accept": "application/json; odata=verbose"
},
contentType: "application/json;odata=verbose",
binaryStringRequestBody: true,
body: fileContents,
success: function (data, textStatus, xhr) {
deferred.resolve(fileName);
},
error: function (xhr, textStats, errorThrown) {
deferred.reject(JSON.stringify(xhr));
}
});
return deferred;
}
function getQueryStringParameter(paramToRetrieve) {
var params =
document.URL.split("?")[1].split("&");
for (var i = 0; i < params.length; i = i + 1) {
var singleParam = params[i].split("=");
if (singleParam[0] == paramToRetrieve)
return singleParam[1];
}
}
And here’s the final code from the Default.aspx page for the SharePoint app:
<%@ Page Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" MasterPageFile="~masterurl/default.master" Language="C#" %>
<%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%-- The markup and script in the following Content element will be placed in the <head> of the page --%>
<asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
<script type="text/javascript" src="../Scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
<script type="text/javascript" src="/_layouts/15/sp.js"></script>
<script type="text/javascript" src="/_layouts/15/SP.RequestExecutor.js"></script>
<script src="../Scripts/rgbcolor.js"></script>
<script src="../Scripts/StackBlur.js"></script>
<script src="../Scripts/canvg.js"></script>
<script src="../Scripts/Base64ToArrayBuffer.js"></script>
<meta name="WebPartPageExpansion" content="full" />
<!-- Add your CSS styles to the following file -->
<link rel="Stylesheet" type="text/css" href="../Content/App.css" />
<!-- Add your JavaScript to the following file -->
<script type="text/javascript" src="../Scripts/App.js"></script>
</asp:Content>
<%-- The markup in the following Content element will be placed in the TitleArea of the page --%>
<asp:Content ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server">
Page Title
</asp:Content>
<%-- The markup and script in the following Content element will be placed in the <body> of the page --%>
<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
<div id="svgWrapper">
<svg id="svgImage" width="640" height="480" xmlns="http://www.w3.org/2000/svg">
<g>
<title>Layer 1</title>
<rect id="svg_1" height="43" width="86" y="57" x="121" stroke-width="5" stroke="#000000" fill="#FF0000"/>
<ellipse ry="57" rx="87.5" id="svg_2" cy="100" cx="332.5" stroke-width="5" stroke="#000000" fill="#FF0000"/>
<path id="svg_3" d="m29,171c-14,26 99,22 99,22c0,0 -4,41 -8,43c-4,2 -39,51 -39,51c0,0 -62,-58 -62,-59c0,-1 10,-57 10,-57z" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="5" stroke="#000000" fill="#FF0000"/>
<path id="svg_4" d="m292.42999,252.03l0,-31.6875l0,0c0,-1.3461 1.0914,-2.4375 2.43869,-2.4375l29.25,0c1.3461,0 2.43631,1.0914 2.43631,2.4375c0,1.34669 -1.09021,2.4375 -2.43631,2.4375l-2.43869,0l0,31.6875c0,1.34669 -1.0914,2.4375 -2.4375,2.4375l-29.25,0l0,0c-1.3461,0 -2.4375,-1.09081 -2.4375,-2.4375c0,-1.3461 1.0914,-2.4375 2.4375,-2.4375l2.4375,0zm4.875,-34.125l0,0c1.3461,0 2.4375,1.0914 2.4375,2.4375c0,1.34669 -1.0914,2.4375 -2.4375,2.4375c-0.67245,0 -1.21875,-0.5457 -1.21875,-1.21875c0,-0.67305 0.5463,-1.21875 1.21875,-1.21875l2.4375,0m24.37619,2.4375l-26.81369,0m-4.875,29.25l0,0c0.67365,0 1.21875,0.5457 1.21875,1.21875c0,0.67305 -0.5451,1.21875 -1.21875,1.21875l2.43869,0m-2.43869,2.4375l0,0c1.3461,0 2.43869,-1.09081 2.43869,-2.4375l0,-2.4375" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="5" stroke="#000000" fill="#FF0000"/>
<text xml:space="preserve" text-anchor="start" id="svg_5" y="146" x="110" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="0" stroke="#000000" fill="#FF0000">
<tspan x="110" dy="0" font-weight="normal" font-style="normal" font-size="24" font-family="DroidSerif" fill="#000000" id="svg_25" xml:space="preserve">Test SVG</tspan>
</text>
</g>
</svg>
</div>
<button type="button" onclick="saveImage();">Export to Doc Lib</button>
<canvas id="exportCanvas" style="display:none;"></canvas>
</asp:Content>