Problem
JavaScript objects allow you to reuse functionality across your forms but there
are a some tricks to getting them to work in the Adobe Acrobat / Reader environment.
Solution
There a a couple of ways to create objects in JavaScript. Using the new operator
and a constructor function, or using the object literal syntax. The new operator
has some advantages as it allows the developer to enforce an initialisation script
be executed and is easily understood by programmers coming from languages that support
classes. Using the literal syntax might require a static method to create the object
or an 'initialise' method ( or 'start' method in the sample below ). But using the
new operator requires a work around that is well described in John Brinkman's blog,
http://blogs.adobe.com/formfeed/2009/09/script_objects_deep_dive.html. This is an
alternative that might fit some situations and uses the literal syntax to create
JavaScript objects defined within an XFA script object.
Detailed explanation
This sample defines an object that can be used for measuring elapsed time, a StopWatch,
and can be used like;
var sw1 = StopWatch.stopWatch("StopWatch1");
sw1.start();
for (var i=0;
i<1000; i++) {} // or something we want to measure
sw1.stop();
sw1.println();
This will output a message on the console that looks like;
StopWatch1:Time elapsed
127ms
Or using a static method to construct the object and achieve the same result;
var sw1 = StopWatch.startNew("StopWatch1");
for (var i=0;
i<1000; i++) {} // or something we want to measure
sw1.stop();
sw1.println();
The script object is called StopWatch and the function defined within it is called
stopWatch, but note that we do not use the new operator to create it. The stopWatch
function returns a inner object that retains access to the variables and functions
of stopWatch without exposing them to the calling code. This is called a closure
and more information about closures and nested functions is available at
https://developer.mozilla.org/en/JavaScript/Reference/Functions_and_function_scope/#Nested_functions_and_closures.
This means there is no way outside code can access the variables
startTime
,
elapsed
,
isRunning
or the function
getElapsedTime.
form1.#variables[0].StopWatch - (JavaScript, client)
function stopWatch(label){
/**
* Private variables used internally by the StopWatch
object.
*/ var
startTime = 0;
var elapsed = 0;
var isRunning =
false;
/**
* A private helper function to return the number
of milliseconds since the
* StopWatch was started.
*/ function
getElapsedTime()
{
return
Date.now() - startTime;
}
/**
* The StopWatch object as visible to other form code.
*/ return
{
/**
* Determines if a StopWatch
is running
* @return {boolean} true
is the StopWatch is running otherwise false.
*/
getIsRunning: function IsRunning()
{
return isRunning;
},
/*
* Returns the total time since
the StopWatch was first started.
* @return {int} The total number
of elapsed milliseconds.
*/
getElapsed: function getElapsed()
{
var result = elapsed;
if (isRunning)
{
result += getElapsedTime();
}
return result;
},
/*
* Starts (or continues), measuring
elapsed time.
*/
start: function start()
{
if (!isRunning)
{
isRunning = true;
startTime = Date.now();
}
},
/*
* Stops (or pauses) measuring elapsed
time.
*/
stop: function stop()
{
if (isRunning)
{
elapsed += getElapsedTime();
isRunning = false;
}
},
/*
* Stops the StopWatch and resets
the elapsed time to zero.
*/
reset:function reset()
{
elapsed = 0;
isRunning = false;
startTime = 0;
},
/*
* Outputs the elapsed time to the
console.
*/
println: function println()
{
console.println(util.printf("%sTime elapsed %,0dms",
(label === undefined) ? "" : label +
":",
this.getElapsed()));
}
}
}
This approach would allow me to pass in an argument to stopWatch and have
different objects returned, perhaps one measuring in decimal time, or more usefully
in a real example having different objects returned depending on a country id passed
in that provided appropriate calculations, validations and initialisations. Creating
objects this way is known as the factory pattern.
I have often used the StopWatch code when experimenting with individual parts of
my form that are not performing fast enough. But I have also modified John Brinkman's
trace macro
http://blogs.adobe.com/formfeed/2010/06/add_trace_to_form_script.html to
add code to all script events in a form. More details of macros and how to install
them can be found in one of his earlier blogs,
http://blogs.adobe.com/formfeed/2010/01/designer_es2_macros.html,
but it should be as simple as creating a directory
C:\Program Files (x86)\Adobe\Adobe
LiveCycle Designer ES2\Scripts\AddExecutionTimes, copy the AddExecutionTimes.js
file to the folder and restart Designer. Once restarted there will be an option
under Tools ... Scripts called AddExecutionTimes.js. If there is any problem running
the macro then the messages should be displayed on the Log tab of the Report window.
Make a copy of the form before running this macro as I don't have a macro to remove
the StopWatch code that gets inserted. Now when you preview your form messages will
appear with the form object somExpression, the event name and the elapsed time,
something like;
xfa[0].form[0].form1[0].#subform[0].TextField1[0].#calculate:Time elapsed 92ms
xfa[0].form[0].form1[0].#subform[0].Table1[0].Row2[0].DecimalField1[0].#calculate:Time
elapsed 58ms
xfa[0].form[0].form1[0].#subform[0].Table1[0].Row2[1].DecimalField1[0].#calculate:Time
elapsed 60ms
xfa[0].form[0].form1[0].#subform[0].Table1[0].Row2[2].DecimalField1[0].#calculate:Time
elapsed 60ms
xfa[0].form[0].form1[0].#subform[0].Table1[0].Row2[3].DecimalField1[0].#calculate:Time
elapsed 92ms
xfa[0].form[0].form1[0].#subform[0].Table1[0].Row2[4].DecimalField1[0].#calculate:Time
elapsed 86ms
xfa[0].form[0].form1[0].#subform[0].TextField1[0].#validate:Time elapsed 57ms
xfa[0].form[0].form1[0].#subform[0].Button1[0].event__click:Time elapsed 2,565ms
The StopWatch object has been modelled after the .net system.diagnostics.stopwatch
object that I have found useful, but that one uses performance counters, this one
uses the JavaScript date object so all timings are just indicative, you may have
to run your tests a number of times to get a more accurate measure and minimise
the number of other processes running on your machine. But hopefully this will point
to parts of your form that are chewing up the most time and help identify candidates
for optimisation.
StopWatch.pdf is a sample with the macro already run, when opened the
console will be displayed with initialisation messages displayed, as you interact
with the form more messages will be displayed.
StopWatch.xdp a script fragment containing the StopWatch object.
AddExecutionTimes.js the macro for adding StopWatch code to all of your
events.