Search This Blog

Friday, 27 September 2013

Adobe LiveCycle Designer Tip #2 - Finding your code

With Designer ES3 came a couple of new options on the hierarchy palette menu, one to show an icon against each form element that contains code and another to show an icon if the form element is bound to a data connection.

The script indicator looks like a scroll and the binding indicator is the one we are used to in the Data View tab.

Friday, 20 September 2013

Adding bookmarks to a Form

Since Designer ES3 came out we have been able to add PDF bookmarks to our forms.  This adds a useful navigation feature and hopefully makes the form more accessible. 

Although Designer would create bookmarks there was no support for them within the Designer UI, you had to go into the XML Source view and enter elements directly, so something like;

<extras name="bookmark">
  <text name="name">Bookmark</text>
  <text name="color">0,0,0</text>
  <text name="style">normal</text>
  <text name="action">gotoPage</text>
<extras>

Radzmar has since written a great macro to generate these elements, http://thelivecycle.blogspot.de/2011/09/xfa-bookmarks.html.

As described in the Adobe Designer help the values for action are;

gotoPage: This is the default value. Focus is shifted to the page where the parent subform starts.
setFocus: Can be used when the parent container is a field. Sets the parent field in focus.
runScript: Triggers JavaScripts to be run.


The gotoPage action seems to go to the top of the page that contains the form control with the bookmark.  This can be confusing for a user if the control is not at the top of the page.

The setFocus action on a subform seems to behave the same as gotoPage and on a field that can receive focus will set focus to that field, but it will also position the field in the middle of the display area, which I didn't like either.

This left me with the runScript action, which I thought I could use my Using Doc.scroll Method In Livecycle Designer method to navigate the form more accurately.  The first thing I found was that the bookmark script runs in the context of the Doc object.  This meant to reference my script object and form controls I had to prefix them with xfa.form.form1, where form1 is the top most subform of my form.  The next thing I found was that event.target did not return the Doc object as it does in a LiveCycle event.  This was not a great problem as the Doc object was my context so I could pass it to my scrollTo method.  Now my bookmark elements look like;

<extras name="bookmark">
  <text name="name">Bookmark</text>
  <text name="color">0,0,0</text>
  <text name="style">runScript</text>
  <text name="action">xfa.form.form1.XFAUtil.scrollTo(xfa.form.form1.Body.PartC, undefined, this);</text>   
<extras>

 
The scrollTo method signature has now changed to;  

/** Scrolls the specified control to the top of the current view and optional sets focus the the specified control.
 *  @param {form object} target   (required)    -- the control to place at the top of the current view.
 *  @param {form object} setFocus (optional)    -- the control that receives focus.
 *  @param {Doc object}  doc      (optional)    -- the Doc object (required when used in a bookmark).
 */
function scrollTo(target, setFocus, doc)
  

Samples

Here are three samples;
Remember that gotoPage and setFocus navigate to the top of the page so there may be no movement at all if you click between two bookmarks that are close together.

Testing

When testing bookmarks in LiveCycle Designer you need to be able to get to the navigation panels that are normally display on the right side of Adobe Reader.  To view these in preview mode press the ESC key.  This will then show the Acrobat toolbar and the navigation panels,  but not there doesn't seem to be the Tools menu available. 

You can also press Crtl + Shift + F5 to open the navigation panels and set focus to them directly.





Monday, 8 July 2013

Adobe LiveCycle Designer Tip #1

One line of code I add to every form is;

event.target.viewState = { pageViewLayoutMode:2, pageViewZoom:0.964 };

This bit of JavaScript I put in the docReady event of the top subform.

The first part, pageViewLayoutMode:2, has the same effect as selecting "Enable Scrolling" from the View ... Page Display menu.  This allows the form to scroll smoothly, especially noticeable when there are large fields, like multiline text boxes or a list box close to the top or bottom of a page.

The second part, pageViewZoom:0.964, has become more important as more people have wide screens.  The default zoom setting for Adobe Reader is fit to width, which with a wide screen can give a letterbox slot view of your form.  If your form has only one page then you could set the zoom level to 100%, but if the form is longer then the vertical scroll bar down the side will take up enough space for a horizontal scroll bar to appear. 

So setting the zoom level to 96.4% is as close to 100% you can go without the horizontal scroll bar appearing.

Sunday, 9 June 2013

Parsing a currency value in a LiveCycle Designer form

If we have a string value like "$1,234.56" that we want to convert into a JavaScript number value we have a several options
.


We could clean the string up, removing the "$" and "," characters and using the parseFloat function.
 


We could call the FormCalc Parse function and an example of calling FormCalc from JavaScript is on John Brinkman's blog.
 


Or we could assign the string to the formattedValue of a hidden DecimalField and then use the rawValue.  This has the same advantages of the FormCalc parse function, with all it's locale processing, without the overhead of calling FormCalc from JavaScript.
 


All we have to do is set the display pattern of the hidden decimal field to suit, in the example that is;

    num{$zzz,zzz,zz9.88}|num{$zzzzzzzz9.88}

So our parseCurrency code now looks like;

function parseCurrency(string)

{
    var result = 0;

    if (string !== null && string !== undefined)

    {
        HiddenDecimalField.rawValue = null;

        HiddenDecimalField.formattedValue = string;

        if (!HiddenDecimalField.isNull)

        {

            result = HiddenDecimalField.rawValue;
        }
    }
    return result;

}


We have set the rawValue to null before assigning the formattedValue because the rawValue will remain unchanged if the value is not valid.
 


One got cha with using DecimalFields is the JavaScript type returned by the rawValue is normally a number but if you clear the "Limit Trailing Digits" checkbox you get a string rawValue,

In this case change;
 


result = HiddenDecimalField.rawValue;


to



result = parseFloat(HiddenDecimalField.rawValue);
 


You can also use this approach to parse a date with a hidden DateTime field.

Download sample form parseCurrency.

Displaying the Acrobat progress bar in a LiveCycle Designer (XFA) Form

NOTE: As of Reader version 21.001.20135 (Feb 2021) it seems you must begin the Thermometer before you can set the value.

 

When Reader has a lengthy operation to complete it displays a progress bar in the bottom right hand corner, for example;


This is available to a Designer form via the Thermometer object, for example;
 


function showProgress(t)

{
    for (var ii = 0; ii < t.duration; ii++)
    {
        for (var i = 0; i < 500000; i++) {}

        t.value += 1;
        t.text = "Step " + ii;
        if (t.cancelled)
        {
            return false;
        }
    }
    return true;

}


var t = app.thermometer;

t.duration = 10;


t.begin();

t.value = 0;

t.text = "";

var completed = showProgress(t);

if (completed)

{
    app.alert("completed");

}

else

{
    app.alert("cancelled");

}


Download the sample form, Thermometer.pdf.



Saturday, 1 June 2013

Using the Doc.scroll method in a LiveCycle Designer Form

It is common to use the xfa.host.setFocus method to position the user on a particular field in the form. Say they have just clicked the submit button and you have detected that they have left a mandatory field empty.  You give them an error message and use xfa.host.setFocus to position them on the field.
 

The problem with this approach is that setFocus always tries to position the field in the middle of the screen.  This may then mean that the question at the top of the screen, were the eye is naturally drawn to, may not be related the message we have just shown the user.
 


We may also want to move to a non-interactive object on the form, maybe a text object that forms a section heading, or some help text.
 


To achieve this we can use the Doc.scroll method, but this means we need to calculate the y coordinate of the object.  The code I have used to perform the calculation comes from John Brinkman's blog, Layout Methods to Find Page Positions.  All we then need to do is rotate the coordinate (so the point 0,0 is at the bottom left corner of the page)
.


This method is written in JavaScript but is dependent on the FormCalc function UnitValue. To use this code in your forms you need to copy the first subform in the sample called FormCalc (see John's blog Calling FormCalc Functions from JavaScript for more details in this technique) and the script object XFAUtil. 
  


The scrollTo function definition is;


/** Scrolls the specified control to the top of the current view and optional sets focus the the specified control.
 
 * @param {form object} target   (required) -- the control to place at the top of the current view.
 
 * @param {form object} setFocus (optional) -- the control that receives focus.
 
 */

function scrollTo(target, setFocus)
 


To see this in action download the ScrollTo.pdf sample and select a form object from the drop down and click the "ScrollTo" button or "Set Focus" to compare the two methods.



I'm not sure how widely known it is, but the Adobe Reader keyboard short-cut for returning to the previous position in the form is Alt+Left Arrow (or Command + Left Arrow on a Mac).


Reading image properties in an Adobe LiveCycle Designer Form

A LiveCycle Designer form allows a user to insert a picture using an ImageField.  However, there is nothing built-in to allow access the properties of an image.  This set of script fragments makes the Exif, GPS and XMP properties of a JPEG image available.

You can also use these code fragments to determine if the image is a JPEG, GIF, PNG or TIFF.

If you just want to check the size of an image then you can use the rawValue property but as the value is base 64 encoded you will need to allow for an overhead of 33%, so ImageField1.rawValue.length * 3 / 4 will give a rough image size in bytes.

LiveCycle Designer forms allow for JPG, GIF, TIF, and PNG formats.  An ImageField allows the user to select one of these images but only if the file extension matches one of those four.  If the file extension is JPEG, JPE, or TIFF then the image will not show up in the "Select Image File" dialog.  If this is a problem for your users then you can use the importDataObject, something like;

if (event.target.importDataObject("name")) // user did not cancel the "select a data file to import" dialog
{
 var attachmentObject = event.target.getDataObject("name");
 var filetype = attachmentObject.path.substring(attachmentObject.path.lastIndexOf(".") + 1);
 if (filetype === "jpg")
 {
  var imageStream = event.target.getDataObjectContents("name");
  var imageStreamEncoded = Net.streamEncode(imageStream, "base64");
  ImageField1.rawValue = util.stringFromStream(imageStreamEncoded);
 }
}


Back to this sample, to find the date a picture was taken we can now use;

var stream = ByteStream.newByteStream(Base64.decode(ImageField1.rawValue));
var image = JPEG.newJPEG(stream);
if (image.isJPEG)
{
 app.alert(image.getDateTime());
}
else
{
 image = GIF.newGIF(stream);
 if (image.isGIF)
 {
 }
 else
 {
  image = TIFF.newTIFF(stream);
  if (image.isTIFF)
  {
  }
  else
  {
   image = PNG.newPNG(stream);
   if (image.isPNG)
   {
   }
  }
 }
}


There is a bug in the base 64 decode routines built into Adobe Reader but this has not yet proved to be a problem as it only seems to occur towards the end of the file were typically the image data is stored.  However, included in this sample is a JavaScript based base 64 decoded.  This does execute slower so I would only use it if this proves to be a problem, just change the first line to;

var stream = ByteStream.newByteStream(Base64.decode(this.rawValue));

In the sample form there are four options;
  • Turn on JavaScript decoding
  • Display a message if a decode error occurs
  • Stop decoding when the SOF jpeg tag is found.  The only thing that may be missed is the JPEG comment tag, but this seems to be rarely used or superseded by the Exif properties.  This option should speed up the decode.
  • Turn on and off displaying of the decode time.
The code in the sample form starts in the change event of the image field in the top right hand corner (with the "Click here to add photo" caption).  This loads the image and then executes the initialize event of the main ImageField to display all the image properties.  I did it this way because I am using the dataWindow object to allowing scrolling though pictures, without the dataWindow you could perform all processing in the ImageField's change event.



 The form template (without pictures of my cat) can also be downloaded, ImageViewer.xdp