Accessing JavaScript Variables in XSL, Client-Side - javascript

I'm creating reporting from a SharePoint 2007 system via the provided WebServices on the client-side (this is all due to developer restrictions - I know, if I had permission, this entire exercise would be perfectly simple in SharePoint designer).
Currently I have a working report. In JavaScript I pass in three parameters (a "From" date, "To" date and "Business Category") which are used to generate a CAML query to web service. The XML response is returned and (after some formatting clean-up) an XSLT is imported (external file) and applied directly to it and the result is pumped into a DIV on the page. All works well.
At this point however I'd like to actually display the input parameters (dates and category) on the report. I have at least a few two kludgy options to do this:
1) Output the values outside of the display DIV. This will work but isn't very versitile.
2) Ouput place-holders for the values in the XSL and then run through a series of replacements before display. This just feels... odd.
3) Manually add nodes with the desired values to the XML packet before transformation then access them normally in the XSLT. This seems the cleanest to me... but also has some baggage I'm not sure I like.
Is there a "right" way to do this? Any chance one of those is it?
Here's some (abbreviated) code to illustrate:
// Set the URL of the XSL to apply
reportXSLURL = "BusinessCategoryReport.xsl";
// Set the input variables.
var CurCategory = DP_QueryString.get("ForCategory", "first");
var CurFrom = DP_QueryString.get("ForFrom", "first");
var CurTo = DP_QueryString.get("ForTo", "first");
* * Soap Call Censored (Too Hot for the Internet) * *
// Load the data
function ProcessResponse(ResponseText) {
// Create and load the serviceXML
var serviceXML = new DP_XML();
serviceXML.parse(ResponseText);
// Create and Load the XSL
var reportXSL = new DP_XML();
reportXSL.load(reportXSLURL);
// Clean Up
CleanSharePointColumn(serviceXML.Doc, "ows_Duration", "CalculatedField");
CleanSharePointColumn(serviceXML.Doc, "ows_Incident_x0020_Manager_x0028_s_x", "UserList");
CleanSharePointColumn(serviceXML.Doc, "ows_Application_x0020__x0028_EAI_x00", "LookupList");
CleanSharePointColumn(serviceXML.Doc, "ows_Business_x0020_Category", "LookupList");
CleanSharePointColumn(serviceXML.Doc, "ows_Incident_x0020_Start", "DateTime_Min");
CleanSharePointColumn(serviceXML.Doc, "ows_Incident_x0020_End", "DateTime_Min");
// Present the Results
document.getElementById("DataDisplay").innerHTML = serviceXML.Doc.transformNode(reportXSL.Doc);
};

You can pass parameters to an XSLT in javascript, just put
<xsl:param name="myparam" />
in the root of your stylesheet. Look into the setParameter method on the XSLTProcessor object in javascript. Here's a javascript method I use:
function transform(inputXml, xslt) {
var xsltProcessor;
var toReturn;
if (window.XSLTProcessor) {
xsltProcessor = new XSLTProcessor();
xsltProcessor.importStylesheet(xslt);
if (arguments.length > 2 && arguments.length % 2 == 0) {
for (var i = 0; i < Math.floor((arguments.length)/2)-1; i++) {
xsltProcessor.setParameter(null, arguments[2*i+2],arguments[2*i+3]);
}
}
toReturn = xsltProcessor.transformToDocument(inputXml);
}
else if (window.ActiveXObject) {
toReturn = makeDocFromString(inputXml.transformNode(xslt));
}
else {
toReturn = "Unable to transform";
}
return toReturn;
}
Any parameters beyond the first two are treated as name/value pairs to be passed to the xslt as a parameter. inputXml and xslt are both XML Documents.
EDIT: Just noticed, I forgot to mention this function uses a helper method, 'makeDocFromString', that just takes the source of an XML document as a string input, and returns an actual XML document. That function's define elsewhere in that .js, it's not part of the standard libraries.

Related

Google Apps Script Auto Duplicate Sheets between conversion of Excel to Google Sheets

I'm new to the coding game, learning how to code day by day.
Recently I'm hooked into Google App Script, learning to make a simple database.
I've tried to decode and re-code the script but I just can't get it working. It was supposed to convert and replace the existing google sheet instead the script just convert and duplicate the excel into many versions (1,2,3) of sheets from the original excel file.
// Convert the user's stored excel files to google spreadsheets based on the specified directories.
// There are quota limits on the maximum conversions per day: consumer #gmail = 250.
function convertCollection1()
{
var user = Session.getActiveUser(); // Used for ownership testing.
var origin = DriveApp.getFolderById("1dPsDfoqMQLCokZK4RN0C0VRzaRATr9AN");
var dest = DriveApp.getFolderById("1M6lDfc_xEkR4w61pUOG4P5AXmSGF1hGy");
// Index the filenames of owned Google Sheets files as object keys (which are hashed).
// This avoids needing to search and do multiple string comparisons.
// It takes around 100-200 ms per iteration to advance the iterator, check if the file
// should be cached, and insert the key-value pair. Depending on the magnitude of
// the task, this may need to be done separately, and loaded from a storage device instead.
// Note that there are quota limits on queries per second - 1000 per 100 sec:
// If the sequence is too large and the loop too fast, Utilities.sleep() usage will be needed.
var gsi = dest.getFilesByType(MimeType.GOOGLE_SHEETS), gsNames = {};
while (gsi.hasNext())
{
var file = gsi.next();
if(file.getOwner().getEmail() == user.getEmail())
gsNames[file.getName()] = true;
}
// Find and convert any unconverted .xls, .xlsx files in the given directories.
var exceltypes = [MimeType.MICROSOFT_EXCEL, MimeType.MICROSOFT_EXCEL_LEGACY];
for(var mt = 0; mt < exceltypes.length; ++mt)
{
var efi = origin.getFilesByType(exceltypes[mt]);
while (efi.hasNext())
{
var file = efi.next();
// Perform conversions only for owned files that don't have owned gs equivalents.
// If an excel file does not have gs file with the same name, gsNames[ ... ] will be undefined, and !undefined -> true
// If an excel file does have a gs file with the same name, gsNames[ ... ] will be true, and !true -> false
if(file.getOwner().getEmail() == user.getEmail() && !gsNames[file.getName()]
{
Drive.Files.insert (
{title: file.getName(), parents: [{"id": dest.getId()}]},
file.getBlob(),
{convert: true}
);
// Do not convert any more spreadsheets with this same name.
gsNames[file.getName()] = true;
}
}
}
}
The logic of the script is spot on. However, there are subtle nuisances in the function .getName() and Drive.Files.insertyou use that is causing the unintended behavior in the code.
folder.getName() gets the full name of the file, which includes the .xls/.xlsx extension. However, when you convert these files with Drive.Files.insert the extension is dropped. So your gsNames object has filenames without extension and but when the code tries to access the specific element using !gsNames[file.getName()] which has the file extension. It always returns undefined, which is evaluated as True.
Hence, you will need to remove the file extension before you try to check if the same file has already been converted. The regex to remove file extension was shamelessly copied from here. Your logic would be modified like so:
if(file.getOwner().getEmail() == user.getEmail() && !gsNames[file.getName().replace(/\.[^/.]+$/, "")])
Your file code will be:
function convertCollection1()
{
var user = Session.getActiveUser(); // Used for ownership testing.1aJcbdGhwliTs_CZ-3ZUvQmGRDzBM7fv9
var origin = DriveApp.getFolderById("1dPsDfoqMQLCokZK4RN0C0VRzaRATr9AN");
var dest = DriveApp.getFolderById("1M6lDfc_xEkR4w61pUOG4P5AXmSGF1hGy");
// Index the filenames of owned Google Sheets files as object keys (which are hashed).
// This avoids needing to search and do multiple string comparisons.
// It takes around 100-200 ms per iteration to advance the iterator, check if the file
// should be cached, and insert the key-value pair. Depending on the magnitude of
// the task, this may need to be done separately, and loaded from a storage device instead.
// Note that there are quota limits on queries per second - 1000 per 100 sec:
// If the sequence is too large and the loop too fast, Utilities.sleep() usage will be needed.
var gsi = dest.getFilesByType(MimeType.GOOGLE_SHEETS), gsNames = {};
while (gsi.hasNext())
{
var file = gsi.next();
if(file.getOwner().getEmail() == user.getEmail())
gsNames[file.getName()] = true;
Logger.log(JSON.stringify(gsNames))
}
// Find and convert any unconverted .xls, .xlsx files in the given directories.
var exceltypes = [MimeType.MICROSOFT_EXCEL, MimeType.MICROSOFT_EXCEL_LEGACY];
for(var mt = 0; mt < exceltypes.length; ++mt)
{
var efi = origin.getFilesByType(exceltypes[mt]);
while (efi.hasNext())
{
var file = efi.next();
// Perform conversions only for owned files that don't have owned gs equivalents.
// If an excel file does not have gs file with the same name, gsNames[ ... ] will be undefined, and !undefined -> true
// If an excel file does have a gs file with the same name, gsNames[ ... ] will be true, and !true -> false
if(file.getOwner().getEmail() == user.getEmail() && !gsNames[file.getName().replace(/\.[^/.]+$/, "")])
{
Drive.Files.insert (
{title: file.getName(), parents: [{"id": dest.getId()}]},
file.getBlob(),
{convert: true}
);
// Do not convert any more spreadsheets with this same name.
gsNames[file.getName()] = true;
}
}
}
Logger.log(JSON.stringify(gsNames))
}
Note the use of Logger.log(), use this in the future to determine what the program might be accessing vs what you think it is doing.
Cheers!

How to read an attachment content into an array or String by clicking a button on XPage?

I have an XPage with an File Upload/Download control that shows my attachments. I need to read a content of first file attachment (name not known/random) into a string var or array by clicking a button.
I am not sure if XMLHttpRequests() can work on XPage or if there is an standard XPages control to do that?
I do need just to read content. (Users don't need to interact with attachment directly (select/save/other UI actions)).
You need to clarify what "first" means: oldest, attached first, first in alphabet? Domino doesn't guarantee a sequence. You can use #AttachmentNames in an evaluate statement. You then get use that name to directly access that attachment from your browser using a rest call using this syntax:
http(s)://[yourserver]/[application.nsf]/[viewname|0]/[UNID| ViewKey]/$File/[AttachmentName]?Open
More details are in this blog entry.
If you want to handle that on the server side then you use document.getAttachment().
Working example:
importPackage(java.net);
importPackage(java.io);
var valString:String = "";
var nrt:NotesRichTextItem=document1.getDocument().getFirstItem('Body');
if (nrt!=null){
var eos:java.util.Vector = nrt.getEmbeddedObjects();
if (!eos.isEmpty()) {
var eo:NotesEmbeddedObject = eos.get(0);
var inputReader:BufferedReader = new BufferedReader(new InputStreamReader(eo.getInputStream(), "UTF-16"));
while ((inputLine = inputReader.readLine()) != null) {
valString+=inputLine + "<br>";
}
if (inputReader != null){inputReader.close();}
eo.recycle();
}
}
return valString;

Is there a better way than eval() in this scenario?

It is a web app, using Google Apps Script, running as the user accessing the app.
We have custom data and code for some users.
That custom information is in a text file within the developer's Google Drive, with only View access from the specific user.
The content of that text file could be like below dummy code:
var oConfig = {
some : "OK",
getinfo : function (s) {
return this.some + s;
}
}
In order to get that custom data / code into the app, we can use eval() as shown below:
var rawjs = DriveApp.getFileById(jsid).getBlob().getDataAsString();
eval(rawjs);
Logger.log(oConfig.getinfo("?")); // OK?
My questions are:
Is there a better way to achieve this goal than eval()?
Is eval() secure enough in this case, considering that the text file is only editable by the developer?
Thanks, Fausto
Well, it looks secure enough. But using eval has other problems, like making it difficult to debug your code, and possibly some other problems.
If you're generating such custom data within your code, I imagine the variety of such customizations is enumerable. If so, I'd leave the code within your script and save in Drive just data and use indicators (like function variants names) of how to rebuild the config object in your script. For example:
function buildConfig(data) {
var config = JSON.parse(data); //only data, no code
config.getInfo = this[config.getInfo]; //hook code safely
return config;
}
function customInfo1(s) { return this.some + s; }
function customInfo2(s) { return s + this.some; }
function testSetup() {
//var userData = DriveApp.getFileById(jsid).getBlob().getDataAsString();
var userData = '{"some":"OK", "getInfo":"customInfo1"}'; //just for easier testing
var config = buildConfig(userdata); //no eval
//let's test it
Logger.log(config.getInfo('test'));
}
It seems secure. But, it will make your execution process slower if you have large data in your text file.
I would still suggest to use JSON.parse() instead of eval() to parse your custom data/code.
{
some : "OK",
getinfo : "function(s){return this.some +\" \"+ s;}"
}
var rawjs = DriveApp.getFileById(jsid).getBlob().getDataAsString();
var oConfig = JSON.parse(rawjs, function(k,v){//put your code here to parse function}); // avoid eval()
Logger.log(oConfig.getinfo("?"));

Open XML content in another window, using JavaScript

I understand I cannot save XML content to a local file, because of security restrictions. but is there a way I can show the XML content in another browser window, as
Window.Open(xmlString, . .. );
that would work the same as -
Window.Open(URL, . . .);
I cannot use server-side language.
I can use javaScript \ jQuery. (I already use them to create the XML)
I can have a template XML file, near my HTML. Is there a way to display the template file and change its content ? almost the same as window.open: is it possible open a new window with modify its DOM or How to write JavaScript to a separate window? but I need to change XML nodes, and not HTML.
EDIT 1: try using myXmlWindow.document.write(xmlString)
=> I tried the suggested code -
var xmlString = xml2Str(xmlDocument);
myXmlWindow = window.open();
myXmlWindow.document.write(xmlString);
myXmlWindow.focus();
but it does not display the whole XML content, just the intern node values. and the new window still display "Connecting..." as it did not finish loading the content (missing close tag ???)
maybe I need to tell it is XML content and not HTML ???
my xmlString :
<root><device1>Name</device1><device2/><device3><Temprature_1>23.5</Temprature_1><Temprature_2>23.4</Temprature_2><Temprature_3>23.4</Temprature_3><Temprature_4>23.3</Temprature_4><Temprature_5>23.2</Temprature_5></device3></root>
the displayed content:
Name23.523.423.423.323.2
EDIT 2: my code -
function xml2Str(xmlNode) {
try {
// Gecko- and Webkit-based browsers (Firefox, Chrome), Opera.
return (new XMLSerializer()).serializeToString(xmlNode);
}
catch (e) {
try {
// Internet Explorer.
return xmlNode.xml;
}
catch (e) {
//Other browsers without XML Serializer
// alert('Xmlserializer not supported');
return('Xmlserializer not supported');
}
}
return false;
}
function fShow_xml_in_win() {
var xmlDocument = $.parseXML("<root/>");
var dev1 = xmlDocument.createElement('device1');
var dev2 = xmlDocument.createElement('device2');
var dev3 = xmlDocument.createElement('device3');
dev1.appendChild(xmlDocument.createTextNode('Name'));
xmlDocument.documentElement.appendChild(dev1);
xmlDocument.documentElement.appendChild(dev2);
xmlDocument.documentElement.appendChild(dev3);
var i;
var xNode;
for (i = 0; i < 5; i++) {
xNode = xmlDocument.createElement('Temprature_' + (i+1));
xNode.appendChild(xmlDocument.createTextNode( "myVal " + ((i+1) * 10) ));
dev3.appendChild(xNode);
}
var xmlString = xml2Str(xmlDocument);
alert(xmlString);
xmlString = "<?xml version='1.0' ?>" + xmlString; // I do not know how to add this node using parseXML :(
alert(xmlString);
myXmlWindow = window.open();
myXmlWindow.document.write(xmlString);
myXmlWindow.document.close(); // !! EDIT 3
myXmlWindow.focus();
return false;
}
EDIT 3: solved the "connecting..." problem
I just needed to add myXmlWindow.document.close();
You can open a blank window and then write content to it as follows:
myWindow=window.open('','','width=200,height=100')
myWindow.document.write(xmlString);
myWindow.focus()
You may need to do some work to format your xmlString, but I think this approach will do what you want. If your xmlString is formatted, try adding:
<?xml version="1.0" ?>
to the start of your string.
My understanding from your post, are
1.(From your firts point)
you get xml from somewhere which is not your control. My suggestion is why don't you get as JSON?
2.(From your second point)
If those XML is created by you means, Why aren't you try to write those XML from reference?
For example:
var reference = window.open();
reference.document.write(<some string goes here>)
3.(From your third point)
As per understanding from your second point. You can create xml. So why are you changing after write the document?
Note: Generally XML is used for Server-to-server communication, JSON is used for Server-to-client(browser) communication.

PDF hostContainer callback

Following this SO solution here to notify clients of a click event in a PDF document, how is it possible to notify the client when the PDF gets submitted by the client using this.myPDF.submitForm("localhost/Handler.ashx?r=2) function?
The PDF File is created inside a user control then rendered into a HTML object:
string container = ("<object data='/myfile.pdf' type='application/pdf'></object>");
The JS file attached to the PDF is done like this:
var webClient = new WebClient();
string htmlContent = webClient.DownloadString(fileurl + "pdf_script.js");
PdfAction action = PdfAction.JavaScript(htmlContnent, pdfstamper.Writer);
pdfstamper.Writer.SetOpenAction(action);
And the content of the js file:
this.disclosed = true;
if (this.external && this.hostContainer) {
function onMessageFunc(stringArray) {
try {
this.myPDF.submitForm("http://localhost/Handler.ashx?EmpNo=12345" + "#FDF", false);
}
catch (e) {
}
}
function onErrorFunc(e) {
console.show();
console.println(e.toString());
}
try {
if (!this.hostContainer.messageHandler);
this.hostContainer.messageHandler = new Object();
this.hostContainer.messageHandler.myPDF = this;
this.hostContainer.messageHandler.onMessage = onMessageFunc;
this.hostContainer.messageHandler.onError = onErrorFunc;
this.hostContainer.messageHandler.onDisclose = function () { return true; };
}
catch (e) {
onErrorFunc(e);
}
}
When the submitForm call is made the PDF contents (form fields) get saved successfully and an alert is displayed in the PDF by doing this:
message = "%FDF-1.2
1 0 obj
<<
/FDF
<<
/Status("Success!")
>>
>>
endobj
trailer
<</Root 1 0 R>>
%%EOF");
return message;
What I'm trying to do is to get the PDF to callback the client after the form submit call sent from this client, a way to acknowledge the client that the form has been submitted, not in a form of an alert, but rather, a way to trigger a function in the host (the container, an iframe, object...etc).
The FDF response you used was unknown to me, so I've learned something new from your question. I've studied the AcroJS Reference and the FDF specification in the PDF Reference, and now I have a better understanding of what your code does. Thank you for that.
I assume that you already know how to trigger a JavaScript message in an HTML file using a JavaScript call from a PDF. See the createMessageHandler() in the JavaScript Communication between HTML and PDF article.
I interpret your question as: "How to I invoke this method after a successful submission of the data?"
If there's a solution to this question, it will involve JavaScript. I see that one can add JavaScript in an FDF file, but I'm not sure if that JavaScript can 'talk to' HTML. I'm not sure if you can call a JavaScript function in your initial PDF from the FDF response. If it's possible, you should add a JavaScript entry to your PDF similar to the /Status entry.
The value of this entry is a dictionary, something like:
<<
/Before (app.alert\("before!"\))
/After (app.alert\("after"\))
/Doc [/MyDocScript1, (myFunc1\(\)),
/MyDocScript2, (myFunc2\(\))
>>
In your case, I would remove the /Before and /Doc keys. I don't think you need them, I'd reduce the dictionary to:
<<
/After (talkToHtml\(\))
>>
Where talkToHtml() is a method already present in the PDF:
function talkToHtml() {
var names = new Array();
names[0] = "Success!";
try{
this.hostContainer.postMessage(names);
}
catch(e){
app.alert(e.message);
}
}
I don't know if this will work. I've never tried it myself. I'm basing my answer on the specs.
I don't know if you really need to use FDF. Have you tried adding JavaScript to your submitForm() method? Something like:
this.myPDF.submitForm({
cURL: "http://localhost/Handler.ashx?EmpNo=12345",
cSubmitAs: "FDF",
oJavaScript: {
Before: 'app.alert("before!")',
After: 'app.alert("after")',
Doc: ["MyDocScript1", "myFunc1()",
"MyDocScript2", "myFunc2()" ]
}
});
This will only work if you submit as FDF. I don't think there's a solution if you submit an HTML query string.
In case you're wondering what MyDocScript1 and MyDocScript2 are:
Doc defines an array defining additional JavaScript scripts to be
added to those defined in the JavaScript entry of the document’s name
dictionary. The array contains an even number of elements, organized
in pairs. The first element of each pair is a name and the second
is a text string or text stream defining the script corresponding
to that name. Each of the defined scripts is added to those already
defined in the name dictionary and then executed before the script
defined in the Before entry is executed. (ISO-32000-1 Table 245)
I'm not sure if all of this will work in practice. Please let me know either way.

Categories

Resources