Enter the beauty of base64 encoding in javascript which I found here
I will add this to my javascript code so you can see the changes. I want to first also point out that I still used an xsl transform. Here is the first part of the updated code:
- addprintmenu();
- });
- function addprintmenu() {
- // Attach to the Actions menu
- var oNodes = document.getElementsByTagName("a");
- if (oNodes[c].id.indexOf("_ListActionsMenu") > -1) {
- var oTokens = MMU_ParseNVAttr(oNodes[c], "menuTokenValues");
- var sMenuID = MHash_Item(oTokens, "TEMPLATECLIENTID");
- var oMenu = document.getElementById(sMenuID);
- oMenu.innerHTML = "<ie:menuitem description='Prints the current view' id='nnsyrintListMenuItem' type='option' onMenuClick='javascript:printlist()' iconSrc='/_layouts/images/WPICON.GIF' text='Print View' menuGroupId='200'></ie:menuitem>" + oMenu.innerHTML;
- return;
- }
- }
- setTimeout("addprintmenu()", 500);
- }
- var sTag = "<" + tagName + ">";
- var eTag = "</" + tagName + ">";
- if ((sTagIndex >= 0) && (eTagIndex > sTagIndex))
- return null;
- }
- function printlist() {
- var usr = $().SPServices.SPGetCurrentUser();
- // NOTE: May need to condition usr result if it returns domain\ data to remove domain\
- // var loc = usr.lastIndexOf("\\") + 1;
- // usr = usr.substring(loc, 50);
- var dest = "http://wss_sa/tools/cts/CTSReports/" + usr + ".xml"; // build destination url for user
- var viewguid, camlQuery;
- var xmldata1 = new ActiveXObject("MSXML.DomDocument");
- xmldata1.async = false;
- var xmldata2 = new ActiveXObject("MSXML.DomDocument");
- xmldata2.async = false;
- var xmldata3 = new ActiveXObject("MSXML.DomDocument");
- xmldata3.async = false;
- var xsldata1 = new ActiveXObject("MSXML.DomDocument");
- xsldata1.async = false;
- // Get the view guid by polling the views by the display name. This will get the actual query text so personal views will still work
- $().SPServices({
- operation: "GetViewCollection",
- async: false,
- webURL: L_Menu_BaseUrl,
- listName: "Commitments",
- async: false,
- completefunc: function(xData, Status) {
- }
- });
- }
- });
- // Have the viewguid so get the actual query
- $().SPServices({
- operation: "GetViewHtml",
- webURL: L_Menu_BaseUrl,
- listName: "Commitments",
- viewName: viewguid,
- async: false,
- debug: true,
- completefunc: function(xData, Status) {
- camlQuery = stripcontents(xData.responseXML.xml, "Where");
- }
- });
- camlQuery = "<Query><Where>" + camlQuery + "</Where></Query>";
- var camlViewFields = "<ViewFields><FieldRef Name='Priority' />";
- camlViewFields += "<FieldRef Name='AssignedDepartment' />";
- camlViewFields += "<FieldRef Name='AssignedDivision' />";
- camlViewFields += "<FieldRef Name='AssignedBranch' />";
- camlViewFields += "<FieldRef Name='Title' />";
- camlViewFields += "<FieldRef Name='Serial' />";
- camlViewFields += "<FieldRef Name='FindingNum' />";
- camlViewFields += "<FieldRef Name='CommitmentNum' />";
- camlViewFields += "<FieldRef Name='DueDate' />";
- camlViewFields += "<FieldRef Name='RECD' />";
- camlViewFields += "<FieldRef Name='Status' />";
- camlViewFields += "<FieldRef Name='WorkingDays' />";
- camlViewFields += "</ViewFields>";
- $().SPServices({
- operation: "GetListItems",
- async: false,
- webURL: L_Menu_BaseUrl,
- listName: "Commitments",
- CAMLQuery: camlQuery,
- CAMLViewFields: camlViewFields,
- completefunc: function(xData, Status) {
- //alert(xData.responseXML.xml);
- var rtz = stripcontents(xData.responseXML.xml, "GetListItemsResult");
- try {
- xmldata1.loadXML(rtz);
- }
- catch (e) { }
- xmldata1.transformNodeToObject(xsldata1, xmldata2);
- var output = "<?xml version='1.0'?><?mso-application progid='Excel.Sheet'?>" + xmldata2.xml;
- xmldata3.loadXML(output);
- var flds = "";
- var results = "";
- var desturls = [dest];
- var encoded = Base64.encode(xmldata3.xml); // This is the key!
- $().SPServices({
- operation: "CopyIntoItems",
- sourceurl: "http://wss_sa/tools/cts/PrintTemplates/CTSPrintList.xsl",
- DestinationUrls: desturls,
- Fields: flds,
- Stream: encoded,
- completefunc: function(xData, Status) { alert(xData.responseText);}
- });
- } // end completefunc
- }); // end spservices
- var oxl = new ActiveXObject("Excel.Application");
- oxl.Visible = true;
- var owb = oxl.Workbooks.Open(reqXML.responseText);
- oxl.ActiveSheet.Columns("A:A").ColumnWidth = 9;
- oxl.ActiveSheet.Columns("C:C").ColumnWidth = 50;
- oxl.ActiveSheet.Columns("D:D").ColumnWidth = 15;
- oxl.Selection.Rows.Autofit;
- oxl.Selection.EntireColumn.Autofit;
- oxl.UserControl = true;
- }
So, what does this code do?
The first few functions sets up the Print View action for the list views.
The stripcontents function is used to parse tags and remove content around the requested tag.
The first line of the printlist function gets the text of the view selector for the list view I am on. This is a major piece so that I know what view I am using. The next few lines setup the ActiveX objects needed for this to work including loading the xsl transform file from a document library CTSPrintList.xsl. This is similar to the xsl stylesheet from the first article.
The next thing I do is use SPServices and the GetViewCollection call to get the actual view guid that matches the current view I am on. Then I call the GetViewHtml webservice function to return the actual view's HTML. I then use the small utility function stripcontents to strip the actual Where clause of the CAML query for the view. I then add back the Query and Where pieces of the CAML query and am ready to then use the GetListItems webservice call to get the data from the view.
Once I get the data, I use the stripcontents function again to strip out the GetListItemsResult data which I then translate using the xsl stylesheet.
I then add the proper xml tags to denote this is spreadsheet xml. I then use the base64 functions listed at the bottom of this post to encode the xml to base64. I then use the CopyIntoItems webservice call to copy this xml file to the CTSReports document library using the users name for the file.
The last step is to then launch Excel and then open this file and you will have the report the way you want it!
I think that this is a better approach than the one that I had before. All questions or thoughts are welcome!
The base 64 code from the site mentioned above:
- var Base64 = {
- // private property
- _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
- // public method for encoding
- encode: function(input) {
- var output = "";
- var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
- var i = 0;
- input = Base64._utf8_encode(input);
- while (i < input.length) {
- chr1 = input.charCodeAt(i++);
- chr2 = input.charCodeAt(i++);
- chr3 = input.charCodeAt(i++);
- enc1 = chr1 >> 2;
- enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
- enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
- enc4 = chr3 & 63;
- if (isNaN(chr2)) {
- enc3 = enc4 = 64;
- } else if (isNaN(chr3)) {
- enc4 = 64;
- }
- output = output +
- this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
- this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
- }
- return output;
- },
- // public method for decoding
- decode: function(input) {
- var output = "";
- var chr1, chr2, chr3;
- var enc1, enc2, enc3, enc4;
- var i = 0;
- input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
- while (i < input.length) {
- enc1 = this._keyStr.indexOf(input.charAt(i++));
- enc2 = this._keyStr.indexOf(input.charAt(i++));
- enc3 = this._keyStr.indexOf(input.charAt(i++));
- enc4 = this._keyStr.indexOf(input.charAt(i++));
- chr1 = (enc1 << 2) | (enc2 >> 4);
- chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
- chr3 = ((enc3 & 3) << 6) | enc4;
- output = output + String.fromCharCode(chr1);
- if (enc3 != 64) {
- output = output + String.fromCharCode(chr2);
- }
- if (enc4 != 64) {
- output = output + String.fromCharCode(chr3);
- }
- }
- output = Base64._utf8_decode(output);
- return output;
- },
- // private method for UTF-8 encoding
- _utf8_encode: function(string) {
- string = string.replace(/\r\n/g, "\n");
- var utftext = "";
- for (var n = 0; n < string.length; n++) {
- var c = string.charCodeAt(n);
- if (c < 128) {
- utftext += String.fromCharCode(c);
- }
- else if ((c > 127) && (c < 2048)) {
- utftext += String.fromCharCode((c >> 6) | 192);
- utftext += String.fromCharCode((c & 63) | 128);
- }
- else {
- utftext += String.fromCharCode((c >> 12) | 224);
- utftext += String.fromCharCode(((c >> 6) & 63) | 128);
- utftext += String.fromCharCode((c & 63) | 128);
- }
- }
- return utftext;
- },
- // private method for UTF-8 decoding
- _utf8_decode: function(utftext) {
- var string = "";
- var i = 0;
- var c = c1 = c2 = 0;
- while (i < utftext.length) {
- c = utftext.charCodeAt(i);
- if (c < 128) {
- string += String.fromCharCode(c);
- i++;
- }
- else if ((c > 191) && (c < 224)) {
- c2 = utftext.charCodeAt(i + 1);
- string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
- i += 2;
- }
- else {
- c2 = utftext.charCodeAt(i + 1);
- c3 = utftext.charCodeAt(i + 2);
- string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
- i += 3;
- }
- }
- return string;
- }
- }
Thanks for the GetViewCollection example.
ReplyDeleteFirst off, thank you for providing this information! I'm no expert by any means, but I'm learning my way with SharePoint customization. How do I go about implementing this code? Where do I place this and how can I invoke this code?
ReplyDeleteThank you in advance!
Sorry for the late reply! There are many means to which you can employ this code. However, due to one of the major functions of how it works, you may want to first decide if it will be able to work in your environment. At some point I think I may refactor and improve this to be reusable as maybe a jQuery plugin or something. For now, I mainly used it in a content editor webpart.
DeleteI'm assuming we include the Base64 variable in the script itself? The link you have doesn't work anymore - goes to a domain purchase page.
ReplyDeleteYes, unfortunately that link seems to be dead. So, yes, I do include that in my script code. I may at some point have added it as a reusable piece somewhere but it really doesn't add too much to the code.
DeleteI think I'm almost there now - it's opening the spreadsheet and attempting to import the data. On the line where you open the workbook (defining variable 'owb'), where is the 'reqXML' object being defined? Is that supposed to be xmlData3 instead? It appears to be looking for responseText from an operation, but which one?
ReplyDeleteOOOPS! That should be opening the (dest) url. oxl.Workbooks.Open(dest)
DeleteHope that helps
Yep, that got me a little further! It looks like the stream into user.xml isn't working, though - that file never gets created. The operations returns all of the list items, but then it looks like the "CopyIntoItems" operation is failing with 0 items. Error is "Value does not fall within expected range". Probably something on my end.
DeleteThe script seems to be working now but there's a problem with my Excel XSL file. Do you have a copy of yours you can attach or send me? I created one by copying & pasting the XSL bits from the previous article, but it's not working either - tells me I have bad XSLT.
ReplyDeleteNot sure that I have one readily available, but if you follow the instructions in the first post (http://spevilgenius.blogspot.com/2010/12/using-spservices-and-excel-to-create.html) this should work. I know that in Excel 2013 the save function is presented as Excel XML 2003 format, but this is what you want.
DeleteOk - last issue, hopefully. The script is successfully launching Excel now and uses my template. It doesn't seem to be importing the z:row data, though - I just get the blank Excel template. I'm pretty sure something is wrong in my XSL. If this was a DVWP in SPD, I'd say the data source isn't working.
DeleteOr rather, the XSL isn't reading the data source. I noticed the 'z' node refers to #RowSetSchema, but that's not defined in my XSL anywhere. I don't see it in your example XSL either though, so maybe I'm barking up the wrong tree.
Delete