October 28, 2008

Script Dependencies and "recalculate()"

For older versions of Acrobat, there were a lot of forms which would call xfa.form.recalculate() in various places.  This was to ensure that fields will get updated which depend on other fields that may have changed.  Newer versions of Acrobat (8.1 and higher) are designed to not require so many calls to recalculate(), because the system is smart enough to evaluate your calculate script and figure out what fields it depends on.  The system puts "listeners" on those other fields so that whenever they change, my calculate script gets called automatically.  I don't have to declare the dependency.

For example, I have a Total field that calculates its value from several other fields (Field1, Field2, Field3).   Any time Field2 changes (due to either a calculation or to the user typing something in Field2), the Total calculate script will be called automatically.

But now make this dynamic:  add a repeating subform with Add and Remove buttons so the user can create new instances of the subform.  For example, I have a repeating subform containing a Weight field.  Outside the repeating subform, a Total field has a calculate script which sums up all the instances of that Weight field.  Pretty simple.  

clip_image001

But the listeners aren't correctly added when a new subform instance is created.  My Totals field is listening to the first instance of RepeatingSubform[0].Weight, but when RepeatingSubform [1] is created, Totals isn't listening to changes to RepeatingSubform [1].Weight.  Stefan Cameron talks about this in an older blog post:  http://forms.stefcameron.com/2006/05/20/add-recalculate/

Interestingly, look at the calculate script on Total:

var sum = 0;

var columnArray = xfa.resolveNodes( "RepeatingSubform1[*].Weight1");

for( var i = 0; i<columnArray.length; i++)

{

    sum += columnArray.item(i).rawValue;

}

this.rawValue = sum;

The call to xfa.resolveNodes() happens to cause the listeners to be updated!  This leads to very odd behavior:  if I change RepeatingSubform [1].Weight, it won't update Totals, until I change RepeatingSubform[0].Weight.  Then any additional Weight field changes will cause Total to update correctly!

So if I start with one weight field (W0) and add 3 more, I end up with weight fields like this:

                W0 (RepeatingSubform[0].Weight)

                W1

                W2

                W3

                Total

Then changes to the added ones (W1, W2, W3) won't update the Total - yet.  Changing W0 will update the total, and once you do that, now changes to W1, W2,W3 will work!

Then if I add a W4:

                W0

                W1

                W2

                W3

                W4

                Total

Now changes to W0, W1, W2, W3 will all continue to work but my newly-added W4 won't work (until I change one of W0-W3).

Workaround:

Add script to the indexChange event of RepeatingSubform, which will take care of all newly-added instances; Totals will be recalculated and will now be "listening to" the new instances.  This is more efficient than calling recalculate() on the entire form - it just recalculates that particular field or subform.

Total.execCalculate();   // Note: you can call this on a subform too:  TotalsSubform.execCalculate();

Add the same script to the delete button click() event so that the Total is recalculated when an instance is deleted.  The important part of that code is:

TotalsSubform.execCalculate();  // this avoids having to call the entire form's recalculate() method.

oTargetSubform.instanceManager.removeInstance(oTargetSubform.index);

Continue reading "Script Dependencies and "recalculate()"" »

September 19, 2008

Dot Leaders Feature

If you haven't had the chance, go check out the video explaining the new "Dot Leaders" feature on Adobe's Developer Connection site:

http://www.adobe.com/devnet/livecycle/articles/dot_leader.html

Sharing Custom Objects Through the Object Library

On top of all my top-secret work on Designer "Next", this week I also had to track down the history of a feature change from Designer 7.1 to Designer 8.0 which was causing grief for a customer. After tracking down the change, I also looked into potential workarounds and re-discovered a feature in Designer that I had completely forgotten about (hey - in a tool as big and advanced as Designer, there's lots and lots of features - it's not that hard to lose track of the littler ones.) It was an interesting case and I thought I'd share it on this blog.

Our story starts with an email from a customer (that came to R&D through the Technical Account Manager and our internal eTech group) saying that when they upgraded from Designer 7.1 to Designer 8.2, a feature that they depended on for their workflow was no longer available. Here's an extract of the email:

They are looking to create 3 different types of Object Libraries:
  1. Libraries accessed by the entire office
  2. Team-specific libraries
  3. Individual libraries
They just recently moved to LiveCycle Designer 8.2 and when an end user removed a category from the Object Library palette, it actually deleted the files from the drive (folder) it was located in, which then caused everyone to lose access to the library. A particular example, the end user added the Administration category to her Object Library palette for a specific project. Once she was done with that project, she removed the category from her Object Library palette as she didn’t want to see it or need it anymore. At this point, everyone lost access to the Administration category when she removed it from her Designer client because it deleted the objects from their team’s shared folder (M: drive). They want to be able to add and remove categories without affecting other users as their team work on different projects. With Designer 7 we could do this as they would chose the Remove the group option as shown below:

OriginalDialog.bmp

In Designer 8.0 and above, you now get the following pop-up. The end user had instinctively clicked on the first entry which subsequently removed the group and all its objects as it states. :)

NewDialog.bmp

Anyone know why this behavior changed and what if any other alternatives they have going forward?

This is an interesting scenario. Anyway, the first order of business was to try to track down that particular change, to find clues as to why we would have removed the original "Remove [but don't delete] Group" option. A quick search through our source code repository found the history of this change, which I will share for people who are interested in the gory details of development work:

CL #xxxxxxxx 12/1/2005 4:01:29 PM

- Defect #yyyyyyyyy - Very Difficult to Recover Barcode Library if Removed

I believe that when the "Restore Standard Objects" feature was implemented, we only had one "factory installed" library tab. We now have three (standard, barcodes and custom; four if you count the potential SAP.) The restore standard objects did not really work correctly anymore, as it has not been updated in a long time and the library itself has changed a lot in that time.

In order to make the restore objects feature more universal and more easily maintainable I have put in place the following changes:

- The application now considers the object libraries installed in the app folder (taking language into account of course) to be the "factory installed default" objects. The application now takes these libraries as being default and will restore the objects in any one of them. In the past, the mechanism was to embed the object files into our resources and query there for the defaults. The old method required a lot more maintanance (which I note has not been done in the intervening time), requires more complex code for managing and does not really offer any more benefit than using the installed files.

- When removing groups, there are now 2 options instead of three: delete and move & delete. The deletion now removes the underlying data directory from the file system. (Leaving the directory there was causing problems with the file restore algorithm.)

I bolded the last paragraph, because it contains the key information about why this change was made in the first place: just removing the group but leaving a directory there was breaking the "restore defaults" functionality.

After first reading this, then thinking about the above customer problem, I thought "Uh-oh. We goofed and unthinkingly destroyed a customer workflow without providing an alternative." However, this didn't feel right to me off the bat - the development team is usually very hesitant to remove any features/options/commands from Designer for exactly this type of reason - once it's in place, odds are someone, somewhere depends on the feature and will be unhappy with the change.

I'm the person who made the change described in the change list text above: when I started nosing around the code, lots of the details came back to me and I remembered more about the whole "Object Library Group Management" feature. And it turns out that there's a specific feature to manage the scenario this customer is describing. The feature isn't as fully-developed as I would like, but even without full UI support, it's the way you are supposed to use shared Object Libraries.

So the Object Library has two kinds of groups: “Groups” and “Shared Library Locations”.

ContextMenu.bmp

This is the Object Library's menu. The options we care about here are "Add Group..." and "Shared Library Location..."

In this customer’s scenario, individual libraries should be created and managed by “Add Group”; shared libraries should use the “Shared Library Location”. Some notes about the “Shared Library”: the shared library is configured by one XML file; you can have lots of shared “groups” but they're all managed by one library. Also you can’t, in Designer, remove just one shared group – you’ll disconnect the whole library if you try (although you won’t delete it or the files – just disconnect).

Here’s what you have to do:

The shared library file should have exactly the same format as the LocalLibrary.xml file which can be found in the user’s Designer data directory (i.e. c:\users\youruserid\Appdata\...\Adobe\Designer\9.0\EN\Objects)

Here’s what you could create:

SharedLibrary.xml – save it somewhere (reliable and safe - like \My Documents) on your system:


<?xml version="1.0" encoding="UTF-8"?>
<objectLibraryTabSet>
   <tab name="Shared Accounting Objects" directory="M:\SomeDirectory\Objects" permission="adm"/>
   <tab name="Shared Form Team Objects" directory="\\SomeSharedComputer\Designer\objects" permission="adm"/>
</objectLibraryTabSet>

This file can contain as many <tab> entries as required. The <tab> entries should be changed to reflect the location/names of shared object resources on the network. The directory can point to relative paths, absolute paths, absolute paths to mapped drives (like the shared "M:" drive in the example above), and network paths.

To access the shared objects, choose "Shared Library Location..." and navigate to the SharedLibrary.xml file and press OK. There should now be tabs that have a special icon denoting their "shared" status in the object library:

SharedLibrary.bmp

To get back to the example above: in this scenario, what you want is that each user has their own SharedLibrary.xml file on their own computer. Each person would add <tab> entries to their own file: one entry per shared object location they need to access. So you would have a bunch of entries for enterprise level objects and a bunch of entries for team level objects. Your own personal objects would continue to use the "Group" option, not the "Shared Library" option. If you no longer want certain groups (say you've changed teams), then you would edit SharedLibrary.xml and remove all the <tab> entries that you don't want anymore.

You could also have one SharedLibrary.xml file that everyone has to use on a shared drive somewhere. That has the benefit that only one person would need to manage the ShareLibrary.xml file, but the drawback is that each user does not have a customized set of shared objects - it's all or nothing.

I don't know how widespread the use of shared object library resources is: if it's very common and lots of people were relying on the same functionality as the customers in the email above, it could be worth it for us to add more UI and functionality to manage shared object libraries...

July 23, 2008

Advanced Typography Options

We've added some new typographical options to Designer for 8.2. We now support kerning, horizontal and vertical font scaling and letter spacing.

FontInspector.png

Kerning

Kerning is used to reduce the spacing between glyphs to make text more visually appealing. Designer supports pair kerning, a kerning algorithm that looks at pairs of glyphs and figures out whether the glyphs can be drawn closer together. Certain glyph pairs are highly prone to kerning (like "AV" where the space between the two letters is highly symmetrical) whereas other glyph pairs are not good candidates for kerning. There is no user control over which glyph pairs will be kerned - the kerning is applied based on font metrics and kerning information embedded in the font as well as other algorithmic operations undertaken by Designer.

You can choose to apply kerning to an entire object or to a selection of text - again, whether anything gets kerned is up to Designer, but it will attempt to kern the given text.

If you apply kerning to a selection of text, or if you multi-select objects that have different kerning values, the "Auto Kern" checkbox will display a square rather than a check: this indicates varying kerning states in the current selection.

Font Scaling

Font scaling is used to independently change either the horizontal or vertical sizing of glyphs. Setting the horizontal and vertical scale to the same amount is equivalent to changing the point size (i.e. for a 10pt font, setting the horizontal and vertical scale to 200% is equivalent to doubling the point size. So the result would be identical to setting the font to be 20pt in size.)

Scaling can be applied to an entire object, or to a text selection within the object.

Letter Spacing

Letter spacing is used to force glyphs to render closer together (sort of squishing the letters) or to force letters to render further apart (expanding the text). Unlike kerning, which is applied selectively to certain glyphs that are considered good candidates, letter spacing is applied uniformly to all text.

In order to make letters render closer together, a negative value should be entered. To force the text to expand, enter a positive value.

Like the other properties, letter spacing can be applied to an entire object, or to a selection of text.

The typographical options are fairly straightforward and work like other existing typographical options. Please feel free to post any comments or questions in the comments section.

Hyphenation Under the Hood (HUH)

Designer now supports automatic hyphenation of text on a form. We anticipate that this feature is going to be very popular with some clients (especially with German clients - from what I understand, German is a language that benefits a lot from auto-hyphenation.) In this post, I'll be providing a summary of how the hyphenation feature works in Designer, along with some "under the hood" descriptions of what's really going on when hyphenation is used.

The Nuts and Bolts

At it's base, hyphenation instructions are really an XFA tag <hyphenation> that is a child of the <para> tag. According to the XFA grammar, any <para> tag in the whole form can have a <hyphenation> child and set specific hyphenation settings for that <para> element.

However, we did not choose to expose hyphenation in Designer as a per-<para> property. Instead, hyphenation is implemented on a per-form basis, with each individual text object either having hyphenation on or off.

The way hyphenation works is that the five different variables that control hyphenation are set at the form level and get applied to any object that has hyphenation turned on. The way we chose to implement the form level hyphenation is through the XFA <protos> mechanism. A <proto> is like a snippet of re-usable XFA; you can declare it once and then have any element "use" that proto.

We have one proto definition for a hyphenation node:


<proto>
<field name="designer__defaultHyphenation">
<para>
<hyphenation hyphenate="1" wordCharacterCount="3" remainCharacterCount="2" pushCharacterCount="2"/>
</para>
</field>
</proto>

Then, every object for which hyphenation is turned on gets a <hyphenation> entry on it's <para> element that uses that proto node:

<para vAlign="middle">
<hyphenation use="designer__defaultHyphenation.para.hyphenation"/>
</para>

How Designer Implements Form-Level Hyphenation Settings


There are actually two mechanisms that come into play when setting form-level hyphenation. The first mechanism is the application level hyphenation settings. These settings can be found under Tools | Options | Formatting. These settings control hyphenation options for new forms. So every new form created in Designer will have these hyphenation settings a a default. The goal of this menu is to allow users to customize hyphenation behavior for all forms they create, so they don't have to constantly update the Form Properties of every new form.

Tools.png

The second mechanism that comes into play is to change settings on a per-form basis, in File | Form Properties | Formatting. Changing the settings here only affects the current form; new forms will still inherit the settings from the Tools | Options | Formatting menu. Changing the form-level hyphenation properties has an immediate effect on the current form: once the dialog is dismissed, all hyphenated text in the form should change to reflect the new hyphenation settings. This dialog also contains two buttons that are shortcuts for removing all hyphenation from the form and adding hyphenation to all the elements of the form. Note that these buttons have an instantaneous result - you don't have to press "ok" on the dialog for the action to take place.

Form.png

The proto hyphenation node will only be present if it is needed (i.e. some real node, somewhere in the document, is hyphenated and thus needs to use the proto.) In certain cases, if there is no proto node, but the form has hyphenation settings that differ from the default, then a Hyphenation processing instruction (PI) will be added to the document in order to save the form's hyphenation settings.

How to Use Hyphenation in a Form

When you are actually working with your form, hyphenation is really simple to use. It's one checkbox control on the Paragraph Inspector.

Inspector1.png

A couple of "gotchas" with hyphenation on the form:

  • By default, allowing hyphenation of field captions and static text is disabled. This means that the hyphenation checkbox control will be disabled on the dialog if a static text control is selected. It also means that on a control that has both caption and value text (i.e. a TextField), when the hyphenation checkbox is checked, it will fill with a square rather than a check, indicating a dual-state check (where half the control is hyphenated and the other half is not.) In order to change this behavior, enable the "Allow Hyphenation in Text and Field Captions" option in Tools | Options | Formatting (for all new forms) or in File | Form Properties | Formatting (for the current form).

  • Another situation where the checkbox may fill with a square rather than a check is if multiple objects are selected and some of them have hyphenation enabled and others have it disabled. The checkbox will show the "mixed" square indicator in this situation as well.

Comments on Compatibility


Hyphenation works with XFAF. However, hyphenation cannot be applied to background content.

Using hyphenation can result in target version warnings, since not all versions of Acrobat support all types of hyphenation. For the most part, hyphenation in value text is not supported in earlier versions of Acrobat (< 9.0). Hyphenation in boilerplate text is supported in older version of Acrobat for static forms. If the form is dynamic, then hyphenation of boilerplate will not be supported in older versions of Acrobat (< 9.0).

One last note on compatibility: there is a risk that using hyphenation could cause text shifting in newer versions of Acrobat. Hyphenation depends on hyphenation dictionaries to figure out where to hyphenate text. Hyphenation dictionaries can change over time: it is conceivable that new dictionaries could be introduced that would change where hyphenation points occur, leading to changes in text layout for hyphenated text. These changes would probably be very minor, but it could lead to a difference of adding/losing a line of text. The risk is fairly small, but it does exist. We strongly urge customers who have forms that are sensitive to text shifting not to make use of hyphenation.

Hyphenation and Fragments

(The following is straight from the functional specification for hyphenation:)

Hyphenation in fragments works in the same way as hyphenation in regular forms. That is, any object that is hyphenated in a fragment will have a <hyphenation use=“designer__defaultHyphenation.para.hyphenation”/> child defined under the appropriate <para> element.

When a fragment is created in a separate XDP file, if any object in the fragment contains hyphenation instructions then the fragment will be created with a <proto> element on the root subform that contains a <hyphenation> child inside a named field that is a copy of the <hyphenation> definition from the original form. The use attribute does not need to be updated, since the SOM of the hyphenation proto is the same in the fragment as it was in the original document.

If a fragment is defined within an existing XDP, nothing changes in regards to hyphenation; the <hyphenation use=“designer__defaultHyphenation.para.hyphenation”/> elements are still valid, and continue to point to the root subform’s <proto> <hyphenation> element.

In both of the cases above, when a fragment is referenced from another form, it (potentially) has different hyphenation settings than the form that is referencing the fragment. The fragment’s hyphenation settings are self-contained within the fragment and separate from the referencing form’s fragment settings.

When a referenced fragment is embedded within a form, it loses its unique hyphenation settings; any <hyphenation use=“designer__defaultHyphenation.para.hyphenation”/> elements will be changed so that the use attribute points to the SOM of the root subform’s <proto> hyphenation node. Or, in other words, the fragment will now inherit the form’s hyphenation settings, rather than having different hyphenation settings that are defined in the fragment. This could potentially lead to a change in appearance and layout when a fragment is embedded.

If a hyphenated fragment is embedded within a form that does not have any hyphenation, then the fragment’s hyphenation will be lost (i.e. it still inherits the form’s hyphenation settings, namely “no hyphenation”).

To Conclude

This was an overview of the Hyphenation feature from an "under the hood" perspective. For details on how the feature works from a functional point of view, please read Designer's Help documentation.