Jump to content

Using external scripts


DSweet

Recommended Posts

I have a project that needs to combine a number of multiple page pdf files. My data file contains a separate field for each file - about 95 of them - that is simply an "on/off" switch entry. I'm trying to get away from having to program this in a big honkin' internal rule within the template to make changes down the road more simple. I would like to use a Load function and save off this script externally. That way I can make changes to the script instead of the template if needed.

 

In a similar job I was able to use a Load function within OnRecordStart to turn on or off some pages, but those pages were a part of the template already. These will be in a separate resource folder and simply flow as they need to into OverFlow pages. I want to do it this way so that if these resource pdf files change later on, I won't have to go in and re-program the template for each and every change. The script should simply load in the approriate pdf file via the data entry and then a global function creates the tagged data string for the pdfs.

 

I programmed this within an internal FusionPro rule (testing with only three data fields), and everything worked out fine. The rule called the global function and all pages were built correctly in the output. But when I tried to use the Load function within the either a text rule or an OnRecordStart the output pages are blank.

 

The internal rule that works is

var result = "";
if (Field("Doc1") != "")  {
   result += placePDF(Field("Doc1"))
}
if (Field("Doc2") != "")  {
   result += placePDF(Field("Doc2"))
}
if (Field("Doc3") != "")  {
   result += placePDF(Field("Doc3"))
}
return result;

and the global function is

function placePDF(inputPDF)  {
var outResult = "";
var PDFResourceFile = "./Docs/" + inputPDF;
var pdf = new FusionProResource(PDFResourceFile, "graphic");
var pdfPages = pdf.countPages
for (var i = 1; i <= pdfPages; i++) {
   pdf.pagenumber = i;
   outResult += pdf.content;
   }
return outResult;
}

 

The script that doesn't work is

var result = "";
if (Field("Doc1") != "") {
result += placePDF(Field("Doc1"))
}
if (Field("Doc2") != "") {
result += placePDF(Field("Doc2"))
}
if (Field("Doc3") != "") {
result += placePDF(Field("Doc3"))
}

 

and the calling text rule is

Load("pdfPageScript.js");
return result;

 

The pdfPageScript.js file is in the same location as the template file. At first I had the return command within the script but that gave me a specific error that the return call was invalid. Also, I do have the return tagged-text box checked off in the text rule.

 

I'm using 4.2P1d, in Acrobat 8.1.6, in Windows XP.

 

Yea, I know we're a bit behind here. Still trying to get the high-and-mighties to approve the jump to light-speed.

Link to comment
Share on other sites

I would like to use a Load function and save off this script externally. That way I can make changes to the script instead of the template if needed.

That's a good idea!

The script that doesn't work is
var result = "";
if (Field("Doc1") != "") {
result += placePDF(Field("Doc1"))
}
if (Field("Doc2") != "") {
result += placePDF(Field("Doc2"))
}
if (Field("Doc3") != "") {
result += placePDF(Field("Doc3"))
}

Exactly what do you mean by "doesn't work?" Is this part of the external JS file or is it code in a rule in the job? If it's in the JS file, it needs to be in a function in order to call it from the rule.

and the calling text rule is
Load("pdfPageScript.js");
return result;

I don't understand what you're expecting this rule to do. Is "result" supposed to be a global variable? Again, you need to call a function from the external JS file.

 

In short, what you want to do is put the global function in the external JS file, call Load, and then call the function. So all this should go into the rule in your job:

Load("pdfPageScript.js");

var result = "";
if (Field("Doc1") != "")  {
       result += placePDF(Field("Doc1"))
}
if (Field("Doc2") != "")  {
       result += placePDF(Field("Doc2"))
}
if (Field("Doc3") != "")  {
       result += placePDF(Field("Doc3"))
}
return result;

Of course, the Load() call could go in OnJobStart or the JavaScript globals, so that you're not loading it again for every record.

 

Or you could factor this out a bit more in your external JS file, like so:

function placePDF(inputPDF)  {
if (!inputPDF)
 return "";

var outResult = "";
var PDFResourceFile = "./Docs/" + inputPDF;
var pdf = new FusionProResource(PDFResourceFile, "graphic");
var pdfPages = pdf.countPages
for (var i = 1; i <= pdfPages; i++) {
   pdf.pagenumber = i;
   outResult += pdf.content;
   }
return outResult;
}

function placePDFfromField(fieldName) {
if (!fieldName)
 return "";

return placePDF(Field(fieldName));
}

function placePDFfromFields() {
var result = "";
for (var i = 0; i < arguments.length; i++)
   result += placePDFfromField(arguments[i]);

return result;
}

Then in your rule, you can simply call this:

Load("pdfPageScript.js");
return placePDFfromFields("Doc1", "Doc2", "Doc3");

Or, even better, if you really have 95 or so of these, instead of saying "Doc1", "Doc2", "Doc3", etc., all the way to "Doc95", you could do this in a loop, by adding something like this to the external JS file:

function placePDFfromFields(prefix, max) {
var result = "";
var i = 1;
for (var i = 1; i < (max || 1000); i++) {
   result += placePDFfromField(prefix + i);
}

return result;
}

Then the rule can simply do this:

Load("pdfPageScript.js");
return placePDFfromFields("Doc");

Link to comment
Share on other sites

Thank for the help Dan.

 

I guess I just really don't quite understand the functionality of what can and can't go into an external script and where it can or can't be called from.

 

When I used this sample within a text rule

var result = "";
if (Field("Doc1") != "") {
result += placePDF(Field("Doc1"))
}
if (Field("Doc2") != "") {
result += placePDF(Field("Doc2"))
}
if (Field("Doc3") != "") {
result += placePDF(Field("Doc3"))
}
return result

the function "placePDF" was called correctly and the resulting string looked something like...

 

<graphic file=".\Docs\doc1.pdf" pagenumber="1"><graphic file=".\Docs\doc1.pdf" pagenumber="2"><graphic file=".\Docs\doc1.pdf" pagenumber="3"><graphic file=".\Docs\doc3.pdf" pagenumber="1"><graphic file=".\Docs\doc3.pdf" pagenumber="2"><graphic file=".\Docs\doc3.pdf" pagenumber="3"><graphic file=".\Docs\doc3.pdf" pagenumber="4"><graphic file=".\Docs\doc4.pdf" pagenumber="1">

 

which is the tagged string that allowed all pdf pages to flow one by one into the OverFlow pages of my template. The file correctly built an eight page document.

 

However, when I placed that entry into an exteral file I named "pdfPageScript.js" (removing the return command after FP barked at me the first time during composition with an error message) and then called it from a text fule as

Load("pdfPageScript.js");
return result;

 

I had expected to get the same string from this "return command". But when the composition was completed (without an error message) the output pdf file was one blank page.

 

Can the contents of a rule be placed in an external script and then a Load function adds the contents to the rule as if it was typed into the rule in the first place? For me it doesn't seem to be working out.

 

Instead of typing over and over again like

var result = "";
if (Field("Doc1") != "") {
result += placePDF(Field("Doc1"))
}
if (Field("Doc2") != "") {
result += placePDF(Field("Doc2"))
}
if (Field("Doc3") != "") {
result += placePDF(Field("Doc3"))
}
...
...
if (Field("Doc95") != "") {
result += placePDF(Field("Doc95"))
}
return result;

I simply wanted to edit this rule copy externally so if the header fieldnames change or more are added or something else like that, at most I would have to link the template to a new data format then just edit the external script.

 

Will I still need to put all this within the rule entry itself?

 

Thanks.

Link to comment
Share on other sites

I guess I just really don't quite understand the functionality of what can and can't go into an external script and where it can or can't be called from.

Functions can go into the external JS file, and be called from rules. You can't directly access a global variable. This WILL NOT WORK:

Load("pdfPageScript.js");
return result;

The variable "result" is never assigned to in this context. You need to place this code:

var result = "";
if (Field("Doc1") != "") {
result += placePDF(Field("Doc1"))
}
if (Field("Doc2") != "") {
result += placePDF(Field("Doc2"))
}
if (Field("Doc3") != "") {
result += placePDF(Field("Doc3"))
}
return result

into its own function in the external JS file and then call that function from your rule.

Can the contents of a rule be placed in an external script and then a Load function adds the contents to the rule as if it was typed into the rule in the first place?

Yes, but the "contents" have to go into a function. It can't just be "naked" code sitting around in the JS file.

Instead of typing over and over again like

var result = "";
if (Field("Doc1") != "") {
result += placePDF(Field("Doc1"))
}
if (Field("Doc2") != "") {
result += placePDF(Field("Doc2"))
}
if (Field("Doc3") != "") {
result += placePDF(Field("Doc3"))
}
...
...
if (Field("Doc95") != "") {
result += placePDF(Field("Doc95"))
}
return result;

I simply wanted to edit this rule copy externally so if the header fieldnames change or more are added or something else like that, at most I would have to link the template to a new data format then just edit the external script.

If you want to avoid copying-and-pasting the same code 95 times, you can just implement a loop, as in my previous example. This loop can be in your rule. It doesn't have to be in an external JS file. I'm not sure where you got the idea that you have to use an external script to factor out the parts of your code that might change later.

 

You can just use this logic right inside your rule. No external JS file is necessary:

function placePDF(inputPDF)  {
if (!inputPDF)
 return "";

var outResult = "";
var PDFResourceFile = "./Docs/" + inputPDF;
var pdf = new FusionProResource(PDFResourceFile, "graphic");
var pdfPages = pdf.countPages
for (var i = 1; i <= pdfPages; i++) {
   pdf.pagenumber = i;
   outResult += pdf.content;
   }
return outResult;
}

function placePDFfromField(fieldName) {
if (!fieldName)
 return "";

return placePDF(Field(fieldName));
}

function placePDFfromFields(prefix, max) {
var result = "";
var i = 1;
for (var i = 1; i < (max || 1000); i++) {
   result += placePDFfromField(prefix + i);
}

return result;
}

return placePDFfromFields("Doc");

And if the field names change from "Doc1", "Doc2", "Doc3", etc., to something else, you still only need to change that very last line. That's all you need to do.

 

Now, that said...

Will I still need to put all this within the rule entry itself?

No. You can put it all in the rule, or you can put everything but that last line into the JS file and then simply do this in your rule:

Load("pdfPageScript.js");
return placePDFfromFields("Doc");

Which is exactly what I said in my last post.

Link to comment
Share on other sites

Thanks for you help Dan. I believe that I just tried to put too much into the external script and just thought it would act like some sort of macro exchange/replacement sort of deal directly. It needs to be a function in and of itself.

 

One more thing though sort of related to this. If the fields do continue on with Doc1, Doc2, Doc3 ... Doc95 then later on the cleint completely removes the fields Doc3, Doc8 and Doc23 and then adds Doc96 and Doc97 to the data file is there a rule or function that would be the equivalent of "if that field name does not exist then just skip that loop step and go to the next". I know that the .exists argument will check if the field content exists but can it check if the Field("field name") itself still exists. I'm sort of still trying to get away having to change as little as possible in the future.:o

 

Thank for all your help.

Link to comment
Share on other sites

Thanks for you help Dan. I believe that I just tried to put too much into the external script and just thought it would act like some sort of macro exchange/replacement sort of deal directly. It needs to be a function in and of itself.

Right, it's not like a C header file. You can declare global functions, but that's about it. Global data gets complicated, but you can always declare a function to return whatever data you need from the external script. The problem wasn't really that you tried to put too much into the external script, it's just that you didn't put everything into a function.

One more thing though sort of related to this. If the fields do continue on with Doc1, Doc2, Doc3 ... Doc95 then later on the cleint completely removes the fields Doc3, Doc8 and Doc23 and then adds Doc96 and Doc97 to the data file is there a rule or function that would be the equivalent of "if that field name does not exist then just skip that loop step and go to the next".

Sure, you just need to modify the placePDFfromField function like so:

function placePDFfromField(fieldName)
{
 if (!fieldName)
   return "";

 var value = "";
 try
 {
   value = Field(fieldName);
 }
 catch (e) {}

 return placePDF(value);
}

And the rest should work like you want, skipping non-existent fields.

I know that the .exists argument will check if the field content exists but can it check if the Field("field name") itself still exists.

That's what the try/catch block in the above code does. It catches the exception such as, "In Field(), no field named Doc37" and ignores it, so the value variable is empty, and the placePDF function doesn't do anything.

Link to comment
Share on other sites

I've got my project to work now the way that I want it to. However, for a while I did have a bit of a snag that I would like to have some clarification of if I could.

 

In my script

function placePDF(inputPDF)  {
if (!inputPDF)
 return "";

var outResult = "";
var PDFResourceFile = "./Docs/" + inputPDF;
var pdf = new FusionProResource(PDFResourceFile, "graphic");
var pdfPages = pdf.countPages
for (var i = 1; i <= pdfPages; i++) {
   pdf.pagenumber = i;
   outResult += pdf.content;
   }
return outResult;
}

the pages were building correctly - almost! I kept getting an error/warning message that stated

Job started 13:50:21 - 1245693021.
Begun composing record #1
The specified Keep conditions could not be honored in the flow <sectionOne>.  Text is truncated.
The specified Keep conditions could not be honored in the flow <sectionTwo>.  Text is truncated.
Job ended 13:50:52 - 1245693052.

The collective pages of the additional graphic pdf files came in correctly, but the very last file to be added to each of the two sections had an extra blank page in front of it and it returned all pages except the last page in the variable pdf file for each section.

 

I did some searching and found other postings that mentioned things like changing the point size of the text box to "1", changing the widows value to something other than the default, and even altering the size of the text box to be slightly larger than the page it is being placed on. None of these worked -- either separately or in any combination. I kept getting the "...specified Keep conditions..." message and the extra blank and missing pdf last page in the final output file. However when I looked at the string being returned by the rule through a validation check, the string contained all the correct pdf file names and all the correct pagenumber listings -- complete with the last page of the last pdf file to be added and no mention of any extra blank page being added.

 

I tried to do a search using the words "specified Keep conditions" but nothing relavent came up from that search.

 

It was at this point that I began to pull out what little is left of the hair on my head. Then I found another post that contained the exact same script for adding multiple pages of a pdf file in a function, but with one step altered. The statement "outResult += pdf.content;" was changed to read: outResult += pdf.content +"\n"; resulting in this script which does work correctly and does not give any error/warning message

function placePDF(inputPDF)  {
if (!inputPDF)
 return "";

var outResult = "";
var PDFResourceFile = "./Docs/" + inputPDF;
var pdf = new FusionProResource(PDFResourceFile, "graphic");
var pdfPages = pdf.countPages
for (var i = 1; i <= pdfPages; i++) {
   pdf.pagenumber = i;
   outResult += pdf.content + "\n";
   }
return outResult;
}

 

What does the error message "...specified Keep conditions..." that I received from the earlier script actually refer to? And why did the addition of the code to add a "new line return" ("\n") cause this error message to correct itself?

Link to comment
Share on other sites

I've got my project to work now the way that I want it to. However, for a while I did have a bit of a snag that I would like to have some clarification of if I could.

Okay, but it's better to start a new thread for new issues or questions.

What does the error message "...specified Keep conditions..." that I received from the earlier script actually refer to?

"Keep conditions" are the conditions under which text should be kept together, at the paragraph level. In FP Desktop, this basically refers to the "Keep with next paragraph" and "Widows" settings in the Paragraph Formatting dialog. Text flows are often broken up into multiple frames, or even multiple columns. (In this case, the "flow" consists of a frame on a Body Page and a variable number of Overflow Page frames.) With these settings, you can tell the composition engine not to break to another frame (or column) in the middle of a paragraph, or to at least avoid leaving only a few lines ("widows") by themselves in frame/column; and you can tell it to keep a paragraph with the next one (such as to keep a section heading with the "body" text below it).

 

If you have a very long paragraphs that don't fit within any particular column and the text can't be kept together in the way that you've told it to ("specified") with these settings, then you get the message you're seeing. The message even tells you which flow triggered the problem, if you've named it (text frame names are actually text flow names).

 

Now, when you use an inline graphic, you're inserting that entire graphic into a flow of text, and basically "auto-leading" the line to be as high as it needs to be to fit the graphic, and it's very easy to get into a situation where the inline graphic simply doesn't fit into the text frame, or at least causes a few line of text (widows), or a single line, to be by itself in another frame. If you're doing something fairly extreme such as placing a graphic as big as the entire page into a frame, you have to be careful that the page is not bigger than the frame; otherwise, the paragraph containing that inline graphic won't fit, or at least it won't be able to be kept together within the frame. This is especially true if you're using multiple inline graphics chained together, trying to force each one to be its own "paragraph" in its own frame. So really, in this case, "The specified Keep conditions could not be honored in the flow" means, "The graphic doesn't fit in the frame."

And why did the addition of the code to add a "new line return" ("\n") cause this error message to correct itself?

I'm not sure; I can't tell just from the JavaScript code. I'd have to look at the job and investigate things like the size of the inline graphic(s), the size of the frame(s) into which you're inserting them, the keep conditions specified in those frames, the settings in the Overflow Options dialog, and some other things. I would guess that you might need to just make a frame slightly larger.

 

Normally, in a rule which returns tagged text such as this, white space such as newlines is ignored by the tagged markup parser at composition time, and extra newlines and spaces in the returned strings are merely to aid readability at rule validation time (when you click the Validate button). It's possible, though, that there's a subtlety in the parser in which the extra white space allows the "text" (really just the inline graphics) to flow correctly into multiple paragraphs instead of all being forced into the same paragraph, which then does not fit.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...