Search This Blog

Friday, 6 December 2013

Formatting telephone numbers

The phone numbers I have to deal with are always 10 digits, but they can be either a two digit prefix, followed by two sets of four digits or a four digit prefix (starting '04') followed by two sets of three digits. For example (02) 1234 1234 or 0400 123 123.



We don't want our users to have to type the spaces and parenthesis but we do want the phone number field to display them once entered,  ignoring them when typed.  This is when we use the display patterns.


 
Because we also want our users to paste a formatted value into the field, including the spaces and parenthesis, we will start with a Text Field.  If we used a Numeric Field then we won't be able to perform a paste,  if the value contains anything but valid numeric characters then the whole paste would fail.



This means we must add some character filtering code to the change event so only digits are accepted.  Filtering characters means testing xfa.event.change when the characters are coming one at a time, as they do when we are typing or, typically, testing xfa.event.newText when we are pasting a value.



But in this case we also want to set the maximum character limit to 10, this means the maximum length of xfa.event.newText is also set to 10 characters.  This becomes a problem when the pasted value is formatted, in which case it will be either 12 or 14 characters. This is when we must use the xfa.event.fullText, which gives the full value before the truncation has occurred.



So our change event code now becomes;


xfa.event.change=(xfa.event.change.length > 1) ?
                   xfa.event.fullText.replace(/\D/g,""):xfa.event.change.replace(/\D/,"");

That is if the value causing the change is longer than one character, remove all non-digit characters, otherwise if the change is one character and that character is not a digit then ignore it.



The final part of our phone number field is to change the display pattern depending on which type of phone number has been entered, so 
our exit event code becomes;


var formatString = "";
if (!this.isNull)

{
    if (LandLinePhoneNumberRegex.test(this.rawValue))
 
    {
        formatString = "'('99')' 9999 9999";
    }
 
  else
 
    {
        if (MobilePhoneRegex.test(this.rawValue))
        {
            formatString = "9999 999 999";
        }
    }

}

this.format.picture.value = formatString;


Setting a display pattern now means we can test for an invalid phone number using;


TelephoneMobile.rawValue === TelephoneMobile.formattedValue


 
This works because the field will only be formatted when a value matches the display pattern, all other values are display as is and so the formatted value will equal the raw value.

Format strings with single quotes

If you ever want a single quote as part of the display pattern, as in a feet and inches measurement like 6'2" then you will need to use two single quotes and wrap then in single quotes.  So four altogether,

text{9''''9"}

 

Sample Form

Formatting telephone numbers.pdf

Tuesday, 19 November 2013

XML Schema minOccurs="0" handling

If you define a data connection using an XML Schema you can specify a minOccur="0" attribute to allow an element to be omitted from the resulting XML.  So an XML Schema element defined as;

<xs:element name="preferredContactMethod" type="preferredContactMethodType" minOccurs="0"/>

Will produce a data connection description of;

<preferredContactMethod dd:minOccur="0" dd:nullType="exclude"/>

And the resulting XML will not contain a preferredContactMethod element if the value is null.

It is the nullType="exclude" attribute that controls the handling of the empty element.  However, Designer only sets this attribute on simple types.  If the minOccurs="0" is on an element that contains child elements or an attribute (that is complex types) then the nullType="exclude" attribute is not added to the data connection description, which can cause the resulting XML to fail schema validation.

This macro will go though the form data description and add the missing nullType="exclude".

Here is a sample form that shows the differences, ComplexTypeNullTypeExclude.pdf.  The same fields are duplicated with the first set having the default behaviour and the second set that has been updated by this macro.

And here is the macro, AddNullTypeExcludeToComplexTypes.zip.  Extract the files to the macros directory of your Designer ES3 (or later) install and you will now get a "Add dd:nullType="exclude" to complex types" option in the Tools ... Macros menu.  (In Designer ES2 you need to extract the files to the Scripts directory of your install and the menu option will be AddNullTypeExcludeToComplexTypes.

Monday, 28 October 2013

Using app.execDialog() in an Adobe Designer Form

Adobe Designer forms can use the app.execDialog() method to show a custom dialog.  The dialog is defined by a JavaScript object literal and this sample is an Adobe Designer form that allows you to add controls, preview your dialog and generate the required object literal.  This is far from a what-you-see-is-what-you-get interface but it does allow you to build up a hierarchy of controls and achieve some complicated dialogs.

To start lets have a look at a very simple dialog, this dialog


Is produced by the following

  description:
  {

   name: "Errors in form",
   elements: [
    {
     type: "view",
     align_children: "align_row",
     elements: [
      {
       type: "static_text",
       name: "Please enter your name",
      },
      {
       type: "ok",
      }
     ]
    }
   ]
  }


Unfortunately for security reason when used within a Designer form you can't specify the dialog title and you also get a "Warning JavaScript Window".  From the console or a Folder Level script you get the dialog without the warnings.



These dialogs can also be used for input (note the addition of a validate function), such as

  description:
  {
   elements: [
    {
     type: "view",
     elements: [
      {
       type: "static_text",
       name: "Please enter your name",
      },
      {
       width: 200,
       height: 22,
       type: "edit_text",
       item_id: "NAME",
      },
      {
       type: "static_text",
       item_id: "ERR1",
       name: "You must enter your name",
      }
     ]
    },
    {
     type: "ok_cancel",
    }
   ]
  },
  validate : function(dialog)
  {
   var isValid = true;
   var elements = dialog.store();
   if (elements["NAME"] === "")
   {
    dialog.visible({ERR1:true});
    isValid = false;
   }
   else
   {
    dialog.visible({ERR1:false});
   }
   return isValid;
  }


Which give this dialog, when the name has been left blank;


This sample, DialogSample.pdf, contains these two dialogs and also one that show most of the  controls available and the properties that go with them;



These dialogs have a hierarchy of controls, that is controls within container controls.  This quickly becomes a very big form.  A form to design a two level dialog contains 3,384 fields, which is enough to get the cooling fans on my computer spinning.  So I am posting two forms one that supports two levels of controls and one that supports one level (which is still 1,452 fields).

Dialog.pdf (3893k)
Dialog.L1.pdf (1936k)

The JavaScript code generated is written to an attachment of the form. There is a stub generated for the event handler of each field and a stub for the validate method.  Be careful writing code in the validate method as if there is no path that returns true then the only way out of the dialog is using the task manager.

These two samples support a number of controls and properties that are not listed in the manual, but are very useful (in these cases I have made note in the tooltips).  Some of the things not documented are;
    • link_text: a hyper link control
    • mclv: a multi-column list view (or grid)
    • slider: a slider but I have not got this to return a value.
    • ok_help, ok_cancel_help, ok_other_help, ok_other_cancel_help controls
    • separator: draw a line horizontal or vertical with optional caption
    • Heading and Title fonts about 10pt and 12pt respectively
    • margin_width, margin_height properties for the view control
    • back_color, gradient_direction, gradient_type for the view control
    • A Dialog.setForeColorRed() method
    • A Dialog.visible() method to show/hide controls
In this sample controls that support a click event also support some actions, such as enabling/disabling other controls, showing/hiding other controls and setting focus on other controls.  Hopefully there will be enough instructions in the form to show you what I mean.

Images

This form started life to generate the hex encoded string.  This is a representation of a 4 channel (ARGB) 8-bits per channel image, that is 8 JavaScript characters per dot.  So images can start to take up a lot of space.  You can get the hex encoded string using the importIcon, getIcon and iconStreamFromIcon methods.  This sample imposes a size limit an image to about 50,000 dots, this is because Designer has trouble with very long lines and this sample uses an XSLT stylesheet to generate the JavaScript literal and the XSLT processor has a limit of 100 levels of recursion. 

Fragments

Each dialog control is defined by a fragment and each fragment is used in three levels, and have a smaller width at each level.  To override the properties of a fragment you need to use the XML Source view and type in the properties you want to override.  For example if the Image fragment had a Border subform and the Border subform had a Propertoes subform then I could override the width values by adding the highlighted XML.

<subform usehref=".\Fragments\ImageControl.xdp#som($template.#subform.Image)" w="162.299mm">
    <subform name="Border" w="169.298mm">
        <subform name="Properties" w="147.299mm"/>
    </subform>
</subform>

One down side of this approach is you will now get the warning message "This fragment reference has local overrides to one or more properties of the source fragment".

ToolTips

As there a many fields that use the same tooltip I have created a separate dataset and used the setProperty element to assign them to the correct field.  This is done in the XML Source view by adding XML under the xfa:dataset element and referencing them in each form field like "
<setProperty target="assist.toolTip" ref="!toolTips.value.[name==&quot;dialogName&quot;]"/>
", which you also need to add using the XML Source view.

Source

The source template, including all the fragments is in the zip file.  Dialog.zip

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.