PowerBuilder Authors: Chris Pollach, Yeshim Deniz, Jayaram Krishnaswamy, Kevin Benedict, Avi Rosenthal

Related Topics: PowerBuilder

PowerBuilder: Article

Advanced WebDW HTML Generation

Advanced WebDW HTML Generation

Okay, so I've been promoting the inclusion of PB client/server articles, yet here I am writing about the Web DataWindow. Don't stop reading! This article applies to all PB developers, whether n-tier or client/server. That said, I now have to prove that this article is important to client/server developers....

Here's my angle: HTML has become a standard beyond Web development. People doing work unrelated to the Web, even nonprogrammers, need to learn the basics of HTML. Here's an example: I'm a consultant and I invoice my customers using QuickBooks. When I generate my invoices I deliver them as HTML files. Sure, I could deliver them as documents or PDFs, but that assumes the recipients have programs to support my document format (Word 2000) or a PDF viewer. What formats do I know they can read regardless of their software? I can think of only two that support graphics: RTF and HTML. In with the new and out with the old! RTF is dead. It's been replaced by HTML, so that's my format of choice.

Yes, that was a trivial example, but what it shows is that my bookkeeper, who isn't a programmer, has to know something about HTML, even if it's only that HTML is a broadly supported format. For PBDJ I've had to save documents as HTML, as well as for for my Web site. When I need to tweak something after generating the HTML, I simply pop the file open in a text editor and tweak. As software developers you need to know at least the basics of HTML regardless of the type of development you're doing.

My next argument is that it's becoming more common for PB apps, even client/server ones, to use the HTML generation features of the DW. For years we shipped DataWindows around via e-mail or used other methods to make them available to non-PB clients. Our format options are limited, and frankly, WMF doesn't cut it. We could create a PDF and get the exact representation we saw on the monitor, but unfortunately that requires a PDF generation license, which could be costly if we have a large number of workstations or servers that need that capability.

The free way is to use the HTML generated by PB. Once generated, we can ship it off via e-mail, store it in the database, or send it into space. We don't have to care if the reader has proprietary software (PBDK) that can read a proprietary format (PSR). It's HTML; any browser can read it.

Of course, my argument breaks down in several places, especially the cross-browser support, but let's just skip past those trivial issues. My point is that you, as an n-tier developer or a client/server developer, need to know HTML, especially how PB generates HTML for DataWindows.

This isn't an introductory article; we've had them before. If you would like to see an article that describes how to generate HTML for DWs in PB and/or how to get that HTML to the Web, send me an e-mail and I'll consider publishing one in an upcoming issue. This article is an advanced lesson on one Web DataWindow property of significant interest: tabIndex. Hopefully, you'll be a tabIndex guru by the time you finish reading this article.

First, a quick look at how to generate HTML for a DW. Remember, I won't go into the details but will only give a brief example of how to obtain the HTML string for a DW. Step 1 is to tell the DW that it will be used for HTML generation. Step 2 is to get the HTML and put it into a string variable.

// Get the HTML from the Datastore
ls_return = ds1.Object.DataWindow.HTMLDW="Yes"
ls_html = ds1.Object.DataWindow.Data.HTML
The equivalent, using more traditional syntax:

// Get the HTML from the Datastore
ls_return = ds1.Modify("Datawindow.HTMLDW='Yes'")
ls_html = ds1.Describe("DataWindow.Data.HTML")
After that you're on your own. Either e-mail the string or send it to a browser.

Each column on the DataWindow will have some HTML rendered for it. The HTML is nontrivial and could be described in a short article of, say, 100 pages. The properties of the column and of the DW itself determine how the column is rendered. If the DW is ReadOnly, then everything is rendered in HTML as spans. The HTML, when viewed, will look like the DW but you won't be able to edit anything or change column properties via JavaScript. If the DW is not ReadOnly, PB looks at the column properties to determine how to render the column. I'm focusing on TabSequence so I'll skip right past all the questions already popping up in your mind.

If a column is Protected, disabled, DisplayOnly, or any combination of those, PB generates HTML to match...or at least tries to. Of course, DW columns don't have an "enable" property, so when I say disabled for a DW column I mean the TabSequence is 0. Protected refers to the "Protect" property, and DisplayOnly to the "DisplayOnly" property of the text edit styles. If a column is not visible, it doesn't get rendered in HTML at all, though PB does generate some under-the-covers JavaScript so you can still refer to the column using JavaScript and Web DW functions like set item.

An HTML "column", using PB lingo, is called an INPUT. There are several INPUT types but they don't exactly match the column edit styles in PB (e.g., DDLB, DDDW, Edit, etc.). Each INPUT has a number of predefined properties, but they also don't exactly match the DW column properties. Therefore, the Sybase engineers who determined how to generate HTML for the DW had a difficult job to do. They did a good one, but it's still evolving as change requests come in from PB developers.

"tabIndex" is a property of the HTML INPUT. It's like TabSequence in the DW in that it defines the order of tabbing among enabled INPUTs on the HTML page. Table 1 provides a brief description of tabbing behavior for potential values of the tabIndex property.

As with a PowerBuilder client, it's important to be able to control the tab order of the Web DataWindow. Therefore, it's important to know how PB generates the tabIndex property in relation to property settings on the DW itself. First, let's look at the three main properties I mentioned and how they are rendered in HTML.

In the DataWindow the TabSequence property has exactly two states, on and off. On is defined by a value greater than 0, which indicates that column's place in the tab order. A value of 0 not only removes the column from the tab order, but also acts as a disablement switch and disables the column. In effect, the TabSequence property combines the properties for enablement and tab order. This causes a logic problem because it doesn't map directly to HTML INPUT properties. HTML provides separate properties for enablement (.disabled) and tab order (.tabIndex).

An important detail about TabSequence is that PB numbers them in multiples of 10, regardless of the numbers you give them. If you define your TabSequences as 1, 2, 3, 7, 11, 25, as soon as you leave the Tab Order view of the DW painter, PB will reorder them to 10, 20, 30, 40, 50, 60. I like this feature as it allows me to easily move columns in the tab order via code without making room. If the TabSequences were 1, 2, 3, 4, 5 and you wanted to move 5 before 3, you'd have to change 5 to 3, then change 3 to 4 and 4 to 5. PB's renumbering scheme eliminates the need to make room. More on this later.

The Protect property of DW columns provides special behavior similar to that provided by the Protect cell setting in Excel. It's a cell-based property, meaning that each cell can have its own setting rather than one setting always applying to all rows for a column. When a cell is protected, it's in the tab order, can get focus, and the user can highlight and copy the edit value. However, the user cannot change the value. This is nearly identical to the ReadOnly property of the text INPUT type in HTML. In fact, the PB DW Protect property was mapped to the ReadOnly HTML property, but unfortunately, it wasn't mapped in a consistent one-to-one relationship.

The DisplayOnly property is available to a few edit styles of DW columns. It acts like Protect in that the user can click on the cell and copy its value, and the cell is allowed to remain in the tab order. Like Protect, it also doesn't allow the user to change the value. The main difference is that this property is column-specific rather than cell-specific, so the setting for a column affects that column for all rows. With the addition of the Protect property in PowerBuilder 4, the DisplayOnly property lost most of its value and is no longer widely used.

Generating tabIndex
Each of these three properties is considered when PB determines what tabIndex to define for an INPUT. Table 2 describes the HTML rendered for all variations of the three properties. When a cell is enabled (has a TabSequence>0, is not Protected, and is not DisplayOnly), the tabIndex property is assigned a value based on a known equation that I'll tell you about a later. First, I need to fill you in on a few details.

Earlier I described how the DW painter orders the TabSequence values in multiples of 10, thereby leaving room to easily move columns around in the tab order or even add new ones. Unfortunately, the HTML rendered for the DW does not follow that precedent. Instead, the tabIndex values run in series. If you have TabSequence values of 10, 20, 30, 40, 50, 60, you'll get tabIndex values of 1, 2, 3, 4, 5, 6.

Imagine you have two Web DataWindows rendered on your browser. The first has INPUTs with tabIndex 1, 2, 3, 4, 5, and the second has INPUTs with tabIndex 1, 2, 3, 4, 5. How will tabbing behave on the page? In IE the tab order would go 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, resulting in a lot of jumping around on the page. You'd think you would be stuck since PB automatically renumbers the TabSequences in the DW painter and you wouldn't be able to start the TabSequences of the second DW at a higher number like 70.

To address this problem, a new property has been added to the DW: TabIndexBase. This property defaults to 0 but you can set it to any integer value. It acts as an offset so you can specify that the lowest tabIndex value on DW2 should be higher than the highest tabIndex value on DW1. PB doesn't calculate the offset for you; you have to do that yourself. For the earlier example, you'd set the TabIndexBase for DW2 to 5 or higher so that all tabIndex values for DW2 end up being higher than all those in DW1. If you set it to exactly 5, you'd end up with 6, 7, 8, 9, 10. The syntax for setting the TabIndexBase is:


Beginning with PB8, the TabIndexBase property is listed in the HTML Generation tab of the properties view of the DW painter. If your offset is static, you can set it permanently there rather than changing it via code.

For more information on TabIndexBase see the following technical document on Sybase's Web site: http://my.sybase.com/detail?id=1012430.

tabIndex Equation
Taking into consideration the TabIndexBase property and the fact that the tabIndexes run in series rather than in multiples of 10, the tabIndex equation is:

tabIndex = (TabSequence/10) + tabindexbase

For pages that have only one DW, the TabIndexBase is irrelevant, though it may be needed in order for the Web DW to fit into the tab order of non-WebDW elements on the page. For those with more than one DW it's a savior.

Rules of tabIndex Generation
Table 2 shows exactly how the HTML INPUT properties tabIndex, disabled, and ReadOnly are set based on the edit type and the three important DW properties. From that table we can deduce a few rules about how PB generates the tabIndex property:

  1. For columns rendered as Text INPUTs, if the cell is DisplayOnly or Protected, then the HTML INPUT is set to ReadOnly.
  2. For columns rendered as Text INPUTs, the HTML-disabled property is set to true if any one of the following is true: TabSequence=0 or DisplayOnly is checked or Protect > 0.
  3. tabIndex is undefined for all INPUTs rendered as disabled.
  4. tabIndex is always defined for Text INPUTs rendered as enabled.
  5. tabIndex maps directly to the Protect property for nontext edit styles such as checkbox, radiobutton, and DDLB. It's defined for those cells that are not Protected and is undefined for those that are Protected.

Why are those rules important? For years we've developed client/server applications with very powerful and friendly interfaces. Now that we're moving to the Web, we're finding that we can't provide those same fancy features because they can't be easily implemented in HTML. Even simple things like disabling and reenabling cells (INPUTs) can be problematic. This is particularly true when PB renders an INPUT with no tabIndex. By default the tabIndex is undefined, so if I use JavaScript to reenable the INPUT, it will go to the end of the tab order. Even worse, in some cases my DW property settings will result in a cell being rendered as a span, which is just simple display text similar to a statictext on a DW. Therefore, there's no way to reenable them to allow the user to enter data.

For these reasons it's important to analyze your columns up front and determine if you need PB to render them a certain way in HTML. If a column is disabled by default yet you may need to reenable it dynamically, you better make sure PB defines the INPUT with all the properties you'll need when it is reenabled. As this article is already too long, I'll have to end by offering a few recommendations.

1.   For editable DWs, set your DW properties so that PB always renders your columns as INPUTs.

This will make your HTML larger, but will allow you to do a lot of things dynamically using JavaScript that you're accustomed to doing dynamically in your PB clients.

2.   Set your DW properties so that PB always defines a tabIndex on your INPUTs.

This will help you avoid the headaches involved in hard coding tabIndex values into JavaScript in your pages.

3.   Use JavaScript to change your INPUT properties dynamically rather than reloading a page to make a change.

Reloads are time-consuming and unnecessarily interrupt users. JavaScript can be a powerful tool for dynamically changing the look and behavior of a DW on an HTML page. Use it as you would use PowerScript to dynamically change the look and behavior of a DW on a PB client.

Wrap Up
The HTML that PowerBuilder generates for DataWindows is based on moderately complex logic and isn't widely understood. Three very important DW column properties - TabSequence, DisplayOnly, and protect - play a very important part in determining how each cell is rendered. The HTML INPUT tabIndex property must be managed properly to provide you with the capability to dynamically change the DW in ways we took for granted in our PB clients.

In an upcoming article I'll delve deeply into the recommendations I briefly stated, and show you techniques for creating powerful HTML DataWindows.

  • The information presented here was developed after determining which DataWindow properties mapped to which HTML properties via the HTML generator provided in Build 9048 of the PowerBuilder 8.0 application environment.

  • A "+" in the TabSequence column indicates there's a positive Tab- Sequence number associated with the control on the DataWindow.

  • A "Y" in the TABINDEX column indicates that the HTML generator created a TABINDEX property in the generated HTML for that control under those circumstances.

  • "T" means true; "F" means false.

  • Special thanks to Frank Sparks for compiling this table.

    Note that the only scenario that consistently produces an INPUT that can be referenced and changed via JavaScript is when the control is created with a positive TabSequence, DisplayOnly (if applicable) is not selected, and the control is not Protected.

  • More Stories By John Olson

    John D. Olson is a principal of Developower, Inc., a consulting company specializing in software solutions using Sybase development tools. A CPD Professional and charter member of TeamSybase, he is co-editor and author of two PB9 books, and the recipient of the ISUG Innovation and Achievement Award for 2003.

    Comments (0)

    Share your thoughts on this story.

    Add your comment
    You must be signed in to add a comment. Sign-in | Register

    In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.