Search This Blog

Friday, 13 April 2018

An alternative Date Picker for LiveCycle Designer forms

Problem

The standard date/time field that is supplied with Designer is fairly simple with no options to control its behaviour. There is no way to force the use of the date picker, they can always enter a date with the keyboard. There is no way to specify a default date, so if you are after the date of birth you have to click the previous month button so often it becomes unusable. There is no way to programmatically open the date picker. And, there is no way to set min and max dates.

Solution

This alternative has some serious limitations as well as it really just a normal subform I position against a standard text field. To do this the text field has to be within a subform that uses positioned layout so I can calculate the x andy coordinates.

Detailed explanation

I have added some options to control the color and format of the date picker but because this is just a standard subform you can modify it to suit your needs. For example I have also added previous and next year buttons.


The options can be set in the initialise event of the text field, for example, to set the date format, tool tip format and the min and max values (XFAUtil is my helper script object to add values to a fields <desc> element);

XFAUtil.setProperty(this, DatePicker.Controller.Properties.ToolTipFormat.Name, "date.long{}", XFAUtil.ContentType.Text);
XFAUtil.setProperty(this, DatePicker.Controller.Properties.DateFormat.Name, "date.long{}", XFAUtil.ContentType.Text);
XFAUtil.setProperty(this, DatePicker.Controller.Properties.MinValue.Name, new Date(new Date().getFullYear(),0,1), XFAUtil.ContentType.Text);
XFAUtil.setProperty(this, DatePicker.Controller.Properties.MaxValue.Name, new Date(new Date().getFullYear(),11,31), XFAUtil.ContentType.Text);


Then when the text field receives focus, (in the enter event) show the date picker.

DatePicker.Controller.show(this);

And to stop a date being enter via the keyboard, in the change event;

xfa.event.change = "";

The code in the actual date picker is reasonably well commented (at least for me) so I wont discribe it here.


JavaScript Date Handling

There are a number of functions within FormCalc that make converting dates very easy. For example to convert the string "4 Janvier, 2011" to a date we can use the FormCalc function parse("date(fr){EEEE, D MMMM YYYY}", "4 Janvier, 2011").

To do this in JavaScript I use a hidden Date/Time field and the formattedValue, which I would normally use to retrieve a value but we can also assign a value to it;

Download sample Calendar.pdf, or all the .XDP files, Calendar.zip.

Tuesday, 5 July 2016

Escaping the “&” character in a submission email


Within a mailto URL, as used by the EmailSubmitButton, the character "&" has a special meaning as a separator between the headers (to, cc, subject, etc).  In an HTML page a "&" character within the header value can be escaped as %26, but in the mysterious world of LiveCycle Designer forms the “%” needs to be escaped as well, giving us %2526.

 For example;

mailto:?subject=Q%2526A

Generates an email with the subject of “Q&A”.

This sample form uses the following code in the "Submit by Email" button to generate the mailto URL and uses two calls the encodeURIComponent generate the correct escaping for the "&" character .

var encodedAmpersand = encodeURIComponent(encodeURIComponent("&"));
var headers = [];    

if (!To.isNull) headers.push("to=" + To.rawValue);
if (!CC.isNull) headers.push("cc=" + CC.rawValue);
if (!BCC.isNull) headers.push("bcc=" + BCC.rawValue);
if (!Subject.isNull) headers.push("subject=" + Subject.rawValue.replace(/&/g, encodedAmpersand));

if (!Body.isNull) headers.push("body=" + Body.rawValue.replace(/&/g, encodedAmpersand));
        
var mailtoTarget = "mailto:?" + headers.join("&");
 
EmailSubmitButton.event__click.submit.target = mailtoTarget;
EmailSubmitButton.execEvent("click");

Note: This only applies to submission emails using an Email Submit Button.  If you are using app,mailMsg() or a mailto URL in a rich text hyperlink then you just use %26.

The sample form, mailto&.pdf, just tests the above code
Event propagation

The sample form shows the XML of the Email Submit Button as you type into the form fields for To, CC, BCC, Subject and Body.  This was done using event propagation, so the sample will only work in Reader 9.1 or later, though the technic of escaping the “&” will work in earlier versions.  The trick here was to have the change event on a subform.  A subform does not support the change event so Designer does not allow you to enter the code.  However, it does still seem to work you just need to enter the code in the XML Source view.

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.