Managing Tab Stops in Fields

When I was working on my previous blog entry, I struggled a bit with rendering JavaScript source code in fields.  The issue was how to accurately display text with the tab stops that are embedded in the code.  The goal was to have the JavaScript code appear with the same spacing you would see in the source editor.  For this to work, the tab spacing needed to be exactly 4 characters.  The solution involved:

  • Use a non-proportional font to display the text (courier new)
  • Figure out the width of 4 characters: a 10pt character in courier new has a width of 6 points.  4 characters is 24 points
  • Set the default tab for the field.  Since Designer doesn’t expose tab properties for fields, the form uses an initialization script to set the default tab: this.para.tabDefault = "24pt";

Tab Leaders

Having accomplished that much, it seems worthwhile to spend some time showing some of the other cool things you can do with tab settings in fields.  In Acrobat/Reader 9 we introduced tab leaders.  To see how this feature works with static text, check out Stephanie Legault’s tutorial.

That’s fine for static text, but what about fields?  If you want to use tab leaders in your form fields, you need to understand the field properties that control tab behaviours.  One way to get there is to read the relevant portions in the XFA specification.  Tab settings are paragraph properties.  A field’s paragraph properties are defined in the <para> element (Part 2/Template Specification/the para element).  Now, you could fill your head with all that book knowledge or you could code from examples

The first page of the sample shows some variations on tab settings.  Each sample field has an initialization script to set the tab properties.  In each case there is a corresponding field that displays the resulting <para> element syntax.

tabDefault

This property on the <para> element controls the default tab setting.  If you want a tab stop every half inch, this would look like:

<field>
    <para tabDefault="0.5in"/>
</field>

To set this via script:

form1.tabExample1.sample::initialize – (JavaScript, client)
this.para.tabDefault = "0.5in";

tab-stops

The general format of a tab leader is: "[alignment] [leader] location ".  You can string a bunch of these together to specify multiple tabs.  If you look at the sample form you will see some variations.  The alignment ( left (before) / right (after) / center / decimal ) determines how the text is aligned at the tab location.  The leader parameter determines how to fill the space leading up to the tab position — space, dots, rule, characters.  The 3rd parameter is the actual tab position.   Hopefully it’s all self-explanatory from the samples.

Tabs and Data Entry

So far, this discussion has been assuming that we’re rendering data that has embedded tabs.  Do tab settings work with interactive fields?  Well, yes and no.  The tab settings work fine.  The problem is, how exactly do you enter a tab character into an interactive field?  The tab key moves you to the next field.  The workaround the example uses is to define a key sequence that will insert a tab character.  Pressing "shift" + "space" will insert a tab character.  The way this works is that the field has a change event to translate this sequence:

form1.#subform[0].tabExample1.sample::change – (JavaScript, client)
if (xfa.event.change === " " && xfa.event.shift) {
    xfa.event.change = "\t";
}

Tabs and Rich Text

Rich text introduces a  couple of wrinkles wrt tabs. 

The tab character is not handled by XHTML.  XHTML processing rules tell us to collapse all white space down to a space character.  To get past this, we define a special style attribute to specify a tab:

<span style=’xfa-tab-count:1’/>

The attributes that exist on the <para> element are also available as style properties: tab-interval and tab-stop.  With a plain text field, the paragraph properties are constant for the whole field.  With rich text, the paragraph properties can be re-specified with each <p> element.

The second page of the sample form has an example where a table of values is formatted into a plain text and a rich text field.  To see the code, look at the form:ready event of each field.  The script for the plain text variation is much simpler, but the script for the rich text field does some more elaborate formatting.  But back up a moment.  Likely the idea of constructing rich text is new to some of you.  The technique is fairly straightforward — build a string representing the XHTML content and then use the loadXML() method to apply the text to the field.  Using XHTML allows the second field to enhance the output:

  • The tab settings for the first line (paragraph) are different from the rest of the field (center aligned with a space leader)
  • The cents portion of the prices are displayed in a superscript
  • The tab leader is styled — the baseline is raised so the dots appear in the middle of the line rather than the bottom.

The overall effect makes the second menu field a fair bit nicer.  Imagine what someone with artistic skills could do.

Aligned Dots

Did you notice?  The dots in the plain text menu line up with the dots used in the rich text menu.  By default, the dot leaders are aligned between different objects on the page. i.e. the dots are aligned on a grid defined at the page level.

The Deep End

Ambient Attributes

When the rich text is rendered, the display properties are determined by combining the field properties with the properties specified in the XHTML.  An example would help:

<field>
  <font typeface="Myriad Pro"/>
  <value>
    <exData contentType="text/html">
      <body>
        <p>Text 1<span style="font-family:Arial">Text 2</span></p>
      </body>
    </exData>
  </value>
</field>

In this example, the XHTML has not specified a font for the string "Text 1".  Whereas the string "Text 2" is explicitly styled as Arial.  This means that the font for "Text 1" is the ambient font — the font inherited from the field definition — in this case Myriad Pro.  Same applies to
tab settings.  By default the XHTML will inherit the tab settings from the field.  If you want to deviate from the default, then specify it inside a <p> element in the rich text.  That explains why in my rich text menu example I explicitly styled the first paragraph, but allowed the remaining text to inherit the field paragraph properties.

Building Big Strings

XHTML strings can grow very large.  You need to be careful how you build the strings.  Using the " += " operator can lead to performance issues.  Appending to a large string is expensive.  It breaks down to these set of operations:

  1. allocate new storage for the larger string
  2. copy the old string to the new storage
  3. append the new string to the new storage

The larger the string and the more append operations, the more expensive this becomes.  A more efficient alternative is to build an array of small strings and at the end, use the join() method to generate one large string.  Here are two examples for comparison:;

var S = "hello world";
S += " more text";
S += " even more text";

In comparison:

var StringArray = [];
StringArray.push("hello world");
StringArray.push(" more text");
StringArray.push(" even more text");
var S = StringArray.join("");

Of course, in this specific example, the strings are so small that the performance difference would not be noticeable. But when the number of append operations grow, the array technique will become much faster.

2 Responses to Managing Tab Stops in Fields

  1. Wes Freeman says:

    First, thanks for your blog (which I’ve read many entries out of since discovering it a few weeks ago), and thanks for your useful post on using tabs in XFA. It was exactly what I was looking for–trying to get more spaces into my rich text field. It does take some tweaking, even though I’m fairly well versed in getting layout to work in HTML–a lot of my tricks didn’t work in XFA. It’s unfortunate that the UL/OL and LI tags aren’t supported–that sure would have been handy.

    I wanted to comment on your performance optimization using big strings. I thought it was an interesting idea to try the push/join technique, so I ran a few performance tests. I always thought that doing something like this was the fastest (and it turns out that it still is):
    var S = “hello world” +
    ” more text” +
    ” even more text”;

    My test compared the straight + technique, the += technique, and the push/join technique.

    I ran 100k instances through a for loop, each concatenating 5 strings together. I tested with a set of short strings, and again with a set of long strings. My results:
    100k x5 short strings using + : 1821ms
    100k x5 short strings using += : 2119ms
    100k x5 short strings using push/join : 2605ms

    100k x5 long strings using + : 1803ms
    100k x5 long strings using += : 2167ms
    100k x5 long strings using push/join : 2620ms

    Then I ran 100k instances with 20 strings concatenating together, just to see if it makes a difference:
    100k x20 short strings using + : 2111ms
    100k x20 short strings using += : 3240ms
    100k x20 short strings using push/join : 3614ms

    100k x20 long strings using + : 2008ms
    100k x20 long strings using += : 3705ms
    100k x20 long strings using push/join : 4313ms

    Interestingly, comparing short and long strings didn’t make much difference until you get into a larger number of concatenations. In the end, a difference of 500-1000ms over 100k concatenations doesn’t really make a big splash in the grand scheme of things–at least, within the context of a form. My biggest argument for using the simple + method is it’s more readable (assuming you indent it properly), and it protects against javascript copy/paste typos like this that parse fine but don’t do what you think they do at first glance:
    var S3 = “hello world”;
    S2 += ” more text”;
    S3 += ” even more text”;

    I can’t even count how many SQL queries were broken in a project I worked on because of while clauses left out via simple concatenation errors like the above. They ran, but the filters weren’t included, and the javascript couldn’t tell you that there was an error. The same copy/paste typo could be introduced using push/join.

  2. Wes:
    Glad to hear you’ve found the blog useful.
    As for the efficiency of string concatenation, this is by no means an exact science.
    Different JavaScript engines will have different performance characteristics. I found a good discussion of this at:
    http://oreilly.com/server-administration/excerpts/even-faster-websites/writing-efficient-javascript.html
    (scroll down to the section on string concatenation)
    Most of the time, using multiple “+” operators doesn’t work for me, since I’m usually building my string inside a loop and have only one value at a time to append.

    And I see from a later post that you’ve discovered the bulleted lists in Acrobat X. I hope the uptake of version X will be quick so we can all start taking advantage.

    John