Search This Blog

Thursday 30 April 2015

Using the Jasmine testing framework in an Adobe Designer LiveCycle Form

Jasmine is a popular testing framework for JavaScript applications.  There's lots of tutorials on using Jasmine so I wont go into a lot of detail but as an example if we had a form that has a item price field, a quantity field and a total field with Jasmine we can write a test something like;

 describe("Total Calculation: ", function()
 {
  beforeEach(function ()
  {
   TotalPriceCalculation.ItemPrice.rawValue = 1.75;
   TotalPriceCalculation.Quantity = 5000;
   TotalPriceCalculation.Total.execCalculate();
  });

  it("Equals ItemPrice * Quantity", function()
  {
   expect(TotalPriceCalculation.Total.rawValue).toEqual(8750);
  });
  it("Formatted in Australian Dollars ", function()
  {
   expect(TotalPriceCalculation.Total.formattedValue).toEqual("$8,750.00");
  });
 });

This is JavaScript code, describe, is a Jasmine function that takes a description and function as arguments. beforeEach is another function that is executed before every it  function which performs the actual test.  The above test will hopefully give us a result like;


The toEqual function after the expect is one of the standard Jasmine matches, the complete list is;

toBe( null | true | false )
toEqual( value )
toMatch( regex | string )
toBeDefined()
toBeUndefined()
toBeNull()
toBeTruthy()
toBeFalsy()
toContain( string )
toBeLessThan( number )
toBeGreaterThan( number )
toBeNaN()
toBeCloseTo( number, precision )


And the Jasmine framework allows us to add our own matches, so for the XFA environment I have added;

toBeVisible()
toBeInvisible()
toBeHidden()
toBeMandatory()
toBeProtected()
toBeOpen()
toBeValid()
toBeXFANull()
toHaveItemCount( number )
toHaveError( string )
toHaveFontColor ( "r,g,b" )


These are just helper functions target.toBeVisible() is the same as target.presence.toEqual("visible").

Other helper functions added for the XFA environment are

 /**
  * Raises the change event for a specified field
  * @param {XFA object}  field     The target field of the change event
  * @param {string}   newText    The value of the field after the change is applied
  * @param {string}   [change=newText] The value typed or pasted into the field that raises the change event
  * @param {boolean}  [keyDown=false]  The user is using an arrow key (listbox and dropdown only)
  * @param {boolean}  [modifier=false] The user is holding the Ctrl key down
  * @param {boolean}  [shift=false]  The user is holding the shift key down
  */

function execChangeEvent(field, newText, change, keyDown, modifier, shift) {


 /**
  * Raises the exit event for a specified field
  * @param {XFA object}  field     The target field of the exit event
  * @param {various}  rawValue    The value of the field before the exit event called, undefined doesn't update the field
  * @param {string}   [commitKey=3]  How the current value of the field was set: 0 - not set, escape key pressed, 1 - mouse click, 2 - enter key, 3 - tabbing
  * @param {boolean}  [modifier=false] The user is holding the Ctrl key down
  * @param {boolean}  [shift=false]  The user is holding the shift key down
  */

function execExitEvent(field, rawValue, commitKey, modifier, shift) {


 /**
  * Reset fields to their default values
  */

function resetData(/*arguments*/) {


In this sample Jasmine.xdp is a script object which is the standalone Jasmine code download with a fairly small modification to cater for the differences in the Adobe JavaScript environment.  The changes are commented at the start of the code but relate to the app.setTimeOut() function taking a string instead of a JavaScript statement.

Hopefully this sample will give you enough to get you going JasmineSample.pdf.  All the tests in this sample pass but when you get some that fail an exception will be thrown, you may want to set "When exception is thrown" to ignore in Acrobat JavaScript options.

The XFA bootstrap is in JasmineXFA.xdp and the Jasmine test are in JasmineSpec.xdp, all zipped in JasmineXDP.zip with the XDP of the sample.

The JasmineSpec.xdp is a form fragment, the tests are run as part of a click event of the "Run Jasmine Specs" button, the results are displayed in a text object in the same fragment.  The click event must start with the statement;

var jasmine = JasmineXFA.Bootstrap({environment:this})

this, adds the Jasmine functions and the XFA helpers and ends with the statement;

jasmine.getEnv().execute(); 

Which starts the actual tests.

The sample by default generates a pass/fail log to the console log as well. If you find generating output to the console log is getting in the way of your own debug statements you can turn the Jasmine output off by passing the includeConsoleReporter:false option.

var jasmine = JasmineXFA.Bootstrap({environment:this, includeConsoleReporter:false})

I tend to develop the tests as an embedded script object which allows me to use the control-click method of referencing fields, then when ready to be released change the script to a fragment and comment it out.  The XDP file being an XML file allows XML comments <!-- -->.  So in the XML Source view will look like;

<!--subform usehref=".\JasmineSpec.xdp#som($template.#subform.JasmineSpec)"></subform-->

I am using the Jasmine framework version 2.0, the latest is 2.2 but I have not been successful in getting later version to run in the Acrobat environment, again because of the differences in the app.setTimeOut() function.  I'm not sure what I am missing but 2.0 has provided enough functionality for me so far.




Thursday 23 April 2015

Adobe Dialog Manager (ADM) in Acrobat JavaScript: insertEntryInList, removeAllEntriesFromList and insertSeparatorEntryInList

This sample demonstrates the undocumented list methods available when using Adobe Dialog Manager (ADM) in Acrobat JavaScript, also called a custom dialog using the app.execDialog()
method.

These methods, on the JavaScript API > Dialog object, are;

insertEntryInList(object)

removeAllEntriesFromList(item_id)

insertSeparatorEntryInList(item_id)

As they are undocumented they could disappear in future releases of Adobe Reader but they are also used by Reader itself,  if you open a JavaScript debugger console and type IWDistributionServer you will see the source code of one of the JavaScript functions that use it.

This sample implements a ListPicker control, ListPicker.pdf


Once the selections are made the you can move a separator line (the blue line in the selection list) up and down.

The separator line can be added to a list with the load method by using the property name "\-" but seems to always appears at the beginning, so the code which you would expect to add a separator as the second item.

dialog.load({ "LST1" : { "abc" : -1, "\\-" : -2, "def" : -3 } })

Adds it as the first.










To achieve the effect we wanted we would need to use;

dialog.insertEntryInList({ "LST1" : { "abc" : -1 } })
dialog.insertSeparatorEntryInList("LST1");    
dialog.insertEntryInList({ "LST1" : { "def" : -3 } })

Thursday 16 April 2015

Adobe Dialog Manager (ADM) in Acrobat JavaScript: progress_bar

This sample demonstrates the undocumented progress_bar control when using Adobe Dialog Manager (ADM) in Acrobat JavaScript, also called a custom dialog using the app.execDialog()
method.


The control can be defined with the following code;
 


{

  type: "progress_bar",

  width: 100,

  height: 22,

  item_id: "PBAR"

}



 Updates to the position of the progress bar can be made with 
 
          

dialog.load({"PBAR": (100 / this.fieldCount) * validFieldCount});



 This code sets the progress bar to a value between 0 and 100 depending on how many valid fields there are.



I started playing with the progress bar control when attempting to develop a timed dialog using  app.setInterval()  but the timer seems to be paused when a dialog is display (as it is if a menu is opened)
 


Still there are some other uses and this sample uses a progress bar to show how much of the dialog is complete and another to indicate the password strength.


Progress_Bar.pdf 
 


Thursday 9 April 2015

Adobe LiveCycle Designer Tip #9 - The JavaScript console and string literals

One of the little mysteries with working Acrobat JavaScript is how the JavaScript console handles string literals.

If you open Acrobat and then the JavaScript console, (using Ctrl-J) and type

'abc'.length  .. we get a response of 3

and

“abc”.length  .. also gives a response of 3

But if we are using the JavaScript console when performing a PDF Preview from LiveCycle Designer then

'abc'.length  .. still gives us 3

but

“abc”.length  .. returns undefined

Even worse

'a"b"c'.length .. also returns undefined, (that is a double quote within single quotes)

So it seems we can’t use double quotes at all, but we can you just need to escape them (even though JavaScript should not require us to), so

'a\"b\"c'.length .. returns 5

This doesn’t cause problems daily but every now and then … maybe if I’m having trouble with a SOM expression involving a predicate, say something like

Content.dataNode.resolveNode('$.Location.[Level == "Lead"]')

Thursday 2 April 2015

Windows Search and XDP files

By default the Windows Search will only pick up the file properties of XDP files.  But since XDP files are really just XML files we can set the search filter handler to the XML Filter and then our search will look in the element and attribute values of the XDP file.

To do this we need to edit the Registry.  If we look at the registry key for the .xml file type (which has a key of HKEY_CLASSES_ROOT\.xml) we see


To make Windows Search treat XDP files like XML all we need to do copy the PersistentHandler key with a default value of the GUID for the XML Filter which is {7E9D8D44-6926-426F-AA2B-217A819A5CCE} to the XDP class key HKEY_CLASSES_ROOT\.xdp.

Once we have done that, if you go to Index Options, select Advanced ... File Types and scroll down to XDP you will see the XML Filter is being used.


Make sure you have the "Index Properties and File Contents" selected, then go back to Index Settings tab and click the Rebuild button ... wait several hours and your searches will now include the contents of the XDP files.

Thursday 26 March 2015

Handling a full stop in a XML element name


The “.” (full stop) character is valid in an XML name, this means there can be cases were you need to escape a full stop in a SOM expression.

This is the default format when using SQL Server and the FOR XML AUTO option and the table has
a schema, you will end up with XML like;

<ClientMgmt>
<ClientMgmt.ClientDetails name="..." />
<ClientMgmt.ClientDetails name="..." />


In a SOM Expression the full stop needs to be escaped with a “\” (backslash), when we put this in JavaScript code we again need to escape the backslash, so end up with three backslash characters.
$data.resolveNodes('ClientMgmt.ClientMgmt\\\.ClientDetails[*]')
Interestingly when trying the same in the Acrobat JavaScript debugger you need to use four backslash characters.

Thursday 19 March 2015

Going deeper with floating fields

Floating fields allow variable text to be inserted within a text object.  A floating field is referenced within a rich text field using XFA extension to the xHTML <span> element, e.g.

As inserted by the LiveCycle Designer Insert ... Floating Field command you will get something like this in the XML Source

<span xfa:embedType="uri" xfa:embedMode="raw" xfa:embed="#floatingField007456"/>

The full XFA syntax for a floating field is;

<span xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/"
      xfa:embedType="uri | som"
      xfa:embed="<SOM expr> | <uri>"
      xfa:embedMode="raw | formatted"/>

embedType

When the embedType has a value of "uri" then the embed value will be the id of the floating field, prefixed with a "#" symbol.

But you can see from the syntax that there are some other options available.  Sometimes we might create a floating field bind it to our data connection just to display the value embedded in some text.  Say the floating field was bound to $data.formData.firstName then we could change our span element to reference the data connection directly, in which case we don't need the floating field at all.

Another situation you may need to use a SOM expression in an embedded field is when you have multiple levels of repeating data, such as a list of countries within which we have a list of cities


If I wanted to use a floating field to display the country element within the repeating city element, there is no way Designer can make the reference.  Designer will give you a binding expression like $record.counties.country, which will always return you the first country.  In this case we can again change the embedType to "som" but use a form object SOM expression, something like;

<span xfa:embedType="som" xfa:embedMode="raw" xfa:embed="countries.country"/>

This will resolve to the form object with the SOM expression countries.country, which should be the right country for the city we are processing.

embedMode

By default the embedMode is set to "raw", which means only the floating fields value is inserted, all font formatting is taken from the surrounding text.  A value of formatted for embedMode means the font attributes of the inserted text are taken from the inserted field, which can be useful if we need to dynamically update them as in this sample, FloatingFields.pdf.

Thursday 12 March 2015

Adobe LiveCycle Designer Tip #8 - Bringing colour to your workspace (and other settings)

I recently received an upgraded computer, which meant I had to re-install LiveCycle Designer ... and find all my settings again.

Tools ... Options ... Document Handling


Increase the number of files in the Recently Used File List to 10.  This seems to be the maximum, although there is no error when a higher number is entered the value will not be saved.

Set the Create Backup Copy on Save, this will create a _BAK.pdf file (or a _BAK.xdp file) which gives you a chance to recover your work if Designer crashes on you.  The disadvantage of setting this option is when you edit a fragment the _BAK file will be created and the fragment will be duplicated when viewed in the fragment library.  Another way you can recover your work if Designer crashes to look for a PDF file in your Windows %temp% directory with some random looking name _1f7o26cap4d25e8q1t.pdf this is the file that is created whenever you perform a PDF Preview.

Tools ... Options ... Workspace


Under JavaScript Syntax Formatting ... select a custom color for strings and numbers, I use an orange color.  I used to have a boss who set his to red, he never wanted to see any red.  I'm not that strict so set mine to an orange.  The idea is to make you think if these values would be better off in a form variable or a dataset.

Select the Show Line Numbers checkbox, any runtime errors will refer to a line number so showing line numbers makes it easier to find the right line.  Just make sure there are no blank lines at the start of your script as these aren't counted when line numbers are displayed in error messages.

Tools ... Options ... Data Binding


Set the Show Dynamic Properties checkbox, this will enable the option to bind values in a drop down to the data connection

Set the Default Binding for New Subforms to "No Data Binding"

Window ... Drawing Aids


Deselect the Snap to Grid option to allow finer control over the form objects, typically I use flowed subforms but even with a positional subform it is usually easier to align with another object, under the Layout menu or learn the keyboard shortcuts, Ctrl-LeftArrow to align a group of fields on there left boundary, etc.

Tools ... Keyboard Shoutcuts


Add Ctrl-Shift-w for Warp in Subform and Ctrl-Shift-u for Unwarp Subform.  The default keyboard shortcuts are listed in Using Designer ES4 / Working with the Keyboard / Default keyboard shortcuts



Thursday 5 March 2015

SOM Expressions with Relative Indexes

Most SOM expressions I work with have an absolute index number, something like form1[0].[0].Table1[0].Row1[0].TextField1[0] where all the numbers within the square brackets are absolute numbers.  But these numbers can be relative, that is have a positive or negative sign.  So if I was on TextField1 of row 2 and wanted to reference TextField1 of row 1 I could use a SOM expression like;

Row1.resolveNode('Row1[-1].TextField1');

Similarly if I have a series of fields with the same name and so have different indexes, say TextField[0], TextFIeld[1] etc, I can reference the next field, in document order, called TextField with a SOM expression

this.resolveNode('TextField[+1]').rawValue

We can also refer to the next field without knowing it's name using the class name reference (that is with a "#" character).

this.resolveNode('#field[+1]').somExpression

or the next button

this.resolveNodes('#field.(ui.oneOfChild.className == "button")').item(1).somExpression

This sample gives examples of these expressions and uses a relative SOM expression to calculate a running total in a table.

RelativeIndex.pdf

Saturday 14 February 2015

Custom Properties

The XFA specification defines customs properties that can be store in a field, exclGroups (or radio buttons) or subforms under the <desc> and <extras> elements.  These allow us to write scripts that process a form but allow the individual fields and subforms to control that processing.  The example used here is a script that sets all the fields to their default value, except if an allowResetData property is set to false.  Other scenarios might be setting all fields to read only, except some, or some have additional processing, validation frameworks, setting min/max values, etc.

There doesn’t seem to be much difference between using the <desc> or <extras> elements but I tend to use <desc> as the names and values are shown in the Info palette.


The <desc> element is also were the <xs:annotation> information is stored is you are using a XML Schema for your data connection.
 
The XFA for a field with this custom property would then look like;
 
<field name="TextField1" w="62mm" h="9mm">
    <desc>
        <boolean name="allowResetData">1</boolean>
    </desc>
    <ui>
        <textEdit/>
    </ui>
    ...
</field>
 
There is no support for updating these values in LiveCycle Designer but I will include two macros that can be used to update the allowResetData property and hopefully can be used as a guide for updating your own properties  … or you could just edit the XML Source window. 

The data stored under a <desc> element can be typed, so JavaScript references are returned in properties with the appropriate data type.  The exception is the date/time related elements that are returned as strings. 



desc element
JavaScript
Data Type
Value Range
boolean Boolean 0 – false, 1 – true
date String  
dateTime

String  
decimal Number For integers;
9007199254740992 to -9007199254740992;
(Same as JavaScript, that is 253)
For floats
1.79E+308 to 1E-15 (max 15 decimal digits if specified by the fracDigits attribute e.g.
<decimal name="Decimal" fracDigits="15"/>,
Otherwise defaults to 2 decimal places
exData String  
float Number 9007199254740992 to -9007199254740992;
(Same as JavaScript)
For floats
1.79E+308 to 1E-08 (max 8 decimal digits)
image String Prints the number 2 in a width of 5 characters with "0" characters padding
integer Number 2147483647 to -2147483647
Attempting to assign a number outside this range raises an "Operation failed." GeneralError exception
text String  
time String  


I haven’t seen any limits documented, these are the values I have found by playing around. Also remember for decimal and float values that exceed the maximum integer value then the precision starts to fail like all usual float values, e.g.


9007199254740992 + 1 = 9007199254740992
9007199254740994 + 2 = 9007199254740994
9007199254740992 + 3 = 9007199254740996


More about float and decimal values here, http://en.wikipedia.org/wiki/Double-precision_floating-point_format

A value under a <desc> element value can be referenced directly in JavaScript using TextField1.desc.allowResetData.value but if the property does not exist on the form object you will get a “Invalid property get operation; desc doesn't have property 'allowResetData'” exception.  To check if a property exists use the namedItem() method, TextField1.desc.nodes.namedItem("allowResetData"), this will return null if the property does not exist or an object with a value property if it does.

So now the JavaScript function to process the form (or part of the form) could look like this;

function resetData(node)
{
    function pushResetDataList(node)
    {
        var allowResetData = node.desc.nodes.namedItem("allowResetData");
        // default is too allow reset
        if (allowResetData === null || allowResetData.value)
        {
            resetDataList.push(node.somExpression);
        }
    }

    function resetDataInner(node)    
    {
        if (node.className === "exclGroup" && !node.isNull) // don't reset fields that are null
        {
            pushResetDataList(node);
        }
        else
        {
            if (node.className === "field")
            {
                if (node.ui.oneOfChild.className !== "button" && !node.isNull) // buttons always null
                {
                    pushResetDataList(node);
                }
            }
            else
            {
                for
(var i = 0; i < node.nodes.length; i++)
                {
                    var nextNode = node.nodes.item(i);
                    if (nextNode.className === "instanceManager")
                    {
                        if (nextNode.count.toString() !== nextNode.occur.min)
                        {
                            nextNode.setInstances(nextNode.occur.min);
                        }
                    }
                    else
                    {
                        if (nextNode.className === "variables")
                        {
                            var scriptObject = nextNode.resolveNode("Script");
                            if (scriptObject && scriptObject.hasOwnProperty("resetData"))
                            {
                                scriptObject.resetData();
                            }
                        }
                        else
                        {
                            if (nextNode.isContainer && nextNode.className !== "draw")
                            {
                                resetDataInner(nextNode);
                            }
                        }
                    }
                }
            }
        }
    }
    var resetDataList = [];
    resetDataInner(node);
    if (resetDataList.length > 0)
    {
        xfa.host.resetData(resetDataList.join(","));
    }
}   

All subforms can have a script object and this resetData function looks for a script object called “Script” that contains a JavaScript function called “resetData” and if found executes it , the code under the nextNode.className === “variables”.

    var scriptObject = nextNode.resolveNode("Script");
    if (scriptObject && scriptObject.hasOwnProperty("resetData"))
    {
        scriptObject.resetData();
    }

This allows parts of the form to perform any custom operations required when a form is reset.  This example (CustomProperties.pdf) uses this function to remove a file that has been attached to the form.

I use two macros for setting and clearing allowResetData flag.  Once installed you will be able to select the appropriate form objects and run a macro instead of editing the XML Source.  To run select Tools … Macros … “Set allowResetData Flag to False” or Tools … Macros … “Clear allowResetData Flag”.  The macros and macro.xml configuration file are in the zip file (CustomProperties.Macros.zip).

If you haven’t written or installed a macro before then refer to the help page. Designer 10 - Macros

The <exData> element can be used to store rich text and be used to populate a Text control like;
Text1.value.exData.loadXML(TextField1.desc.ExData.saveXML(), true, true);

Likewise the <image> custom property can be used to store image and used to populate an image control like;  Image1.value.image.value = TextField1.desc.Image.value;

Form variables created using Form Properties … Variables are stored under a <variables> element, when created by LiveCycle Designer they are always text, but it is valid for them to be any of the types that can be used under the <desc> element.  This allows references to the variables in JavaScript to be typed, so by editing the XML Source can make your JavaScript code simpler, at least when dealing with Number and Boolean form variables.  Once you have made this change Designer will show them as a “?” icon in the hierarchy palette (see image below) but this has never caused any problem in my forms.

And looks like this in the XML Source window.


<variables>
    <text name="Text"/>
    <integer name="Integer"/>
</variables>



 

Wednesday 28 January 2015

Adobe LiveCycle Designer Tip #7 - Formatting Tooltips

With the tooltips available with LiveCycle Designer there is no options to add formatting to a tooltip.  You can try using a subform and dynamically position it next to the field, like this sample Season Planner (or Year Planner) PDF Template.

One thing you can do is add some carriage returns to add some vertical space.  For a long time I was doing this by editing the XML Source and adding a carriage return character "&#xD;" in the toolTip value.

But you can do the same thing from the Accessibility palette by using a ctrl-Enter key combination. 

Seems everywhere you can enter a rich text value you can use shift-Enter but maybe because the Tool Tip value in the Accessibility palette is not rich text we have to use ctrl-Enter.

You can also gain some control over the tooltip width by using a non-breaking space (Ctrl-Shift-Space, which inserts a 0xc2a0 character) instead of a normal space.

Saturday 10 January 2015

Custom Bulleted and Numbered Lists

With LiveCycle Designer ES3 came support for bulleted and numbered lists under the Paragraph palette.  This did make creating lists a lot easier but also added some restrictions;
          What ES3 gives you is


          What you might have been after
  • There is no option to add a leader (dots between the bullet/number and the text)
           Now you number list can look like
  • When read by a screen reader like NVDA the bullet characters or numbers are not announced.  In the example in the first dot point above, NVDA announces "What is your favourite fruit Apples Oranges Bananas" when what you probably wanted was "What is your favourite fruit bullet Apples bullet Oranges bullet Bananas"
In LiveCycle Designer the Text fields containing rich text that we need to use to implement a bullet list are implemented using a subset of xHTML.  ES3 introduced support for the ol, ul and li tags but prior to ES3 it was also possible to create bullet point lists and numbered list you just had to hand edit the xHTML, and use a negative text-indent and some tab-stops.

This sample PDF Form makes to easy, just enter the text of your dot points, select the type of bullets or numbers, the spacing and if you want leaders or not and the Draw element (which implements the Text object)  is output to the console.

You should see something like;

<draw name="Text1" w="196.85mm" minH="0in" xmlns="http://www.xfa.org/schema/xfa-template/3.6/">
   <value>
      <exData contentType="text/html">
         <body xmlns="http://www.w3.org/1999/xhtml" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/"><p style="text-indent:-10mm;xfa-tab-stops:left leader (dots page 3pt) 10mm"><span style="font-size:9pt">1.</span><span style="xfa-tab-count:1"/><span style="font-size:9pt">Oranges</span></p><p style="text-indent:-10mm;xfa-tab-stops:left leader (dots page 3pt) 10mm"><span style="font-size:9pt">2.</span><span style="xfa-tab-count:1"/><span style="font-size:9pt">Apples</span></p><p style="text-indent:-10mm;xfa-tab-stops:left leader (dots page 3pt) 10mm"><span style="font-size:9pt">3.</span><span style="xfa-tab-count:1"/><span style="font-size:9pt">Bananas</span></p></body>
      </exData>
   </value>
   <ui>
      <textEdit allowRichText="1"/>
   </ui>
   <font typeface="Myriad Pro"/>
</draw>


To add this to your form it might be easiest to add a Text object in the place you need, select it, then switch to the XML Source view.  You should see a <draw>...</draw> element, which you can replace with the one copy and pasted from the JavaScript console.

The sample to generate the Draw element is Bullets.pdf.

A form containing samples of ES3 and customs bullet/number lists and how NVDA reads them is NVDA.Bullets.pdf.