Disclaimer and License

Opinions expressed here by Tim Tripcony are his own and not representative of his employer.

Creative Commons License
Tip of the Iceberg is licensed under a Creative Commons Attribution 3.0 Unported License.
Based on a work at timtripcony.com.

Unless otherwise explicitly specified, all code samples and downloads are copyright Tim Tripcony and licensed under Apache License 2.0.

Search

What the Quote?

"They're all flying to Hawaii to reenact Pearl Habor Day tomorrow."

Steven Rodgers

"Infinity is the new finity"

Eric Romo

"Thank you, that's my elbow. It's kinda like a nipple for my arm."

Laura Tripcony

« org.openntf.xsp.extlib | Main| SSJS is a crutch »

Passthru vs. component - my perspective

Category xpages
Paul Withers posted a thorough article explaining the differences between namespaced XPage components (e.g. <xp:div />) and their corresponding passthru elements (e.g. <div />), providing numerous examples of what actually happens when these objects are constructed. I've always heard (and often repeated) that passthru elements are more efficiently processed than their namespaced equivalents, so Paul's post inspired me to offer my own perspective.

Simply put, there's practically no difference... but there are a couple subtle discrepancies.

The three passthru elements I use most in my own development are div, span, and br. As Paul mentioned, any passthru element becomes an instance of UIPassthroughTag. This class extends UIOutputEx, which also happens to be the same base class from which links, labels, script blocks, computed fields, and several viewpanel-related components are derived. UIOutputEx, in turn, extends UIOutput, which itself extends UIComponentBase. As a result, passthru elements are components, and go through the encode and decode cycle just like any other component.

Similarly, XspDiv, XspSpan, and XspLineBreak all extend UIComponentTag (as do XspParagraph, XspTable, XspTableRow, and XspTableCell). UIComponentTag extends UIComponentBase. So as far as the JVM is concerned, all of these things are very much alike.

There are, however, two potentially significant differences, and since none of the components mentioned above can accept data, both differences are associated with the rendering phase.

The first difference relates to how component attributes are rendered. For these three elements (divs, spans, and line breaks), the same renderer class is used regardless of whether the element is namespaced or passthru. However... as of Domino 9, XspDiv supports 17 named attributes of which the renderer is aware. If, for example, you set a value for the align attribute, the component will be constructed as follows:

XspDiv result = new XspDiv();
result.setAlign("right");
return result;


If, on the other hand, you set a value for the align attribute on a passthru div, its construction will be as follows:

UIPassThroughTag component = new UIPassThroughTag();
component.setTag("div");
component.addAttribute("align", "right");
return component;


Fairly similar, yes? But with one key difference: in an XspDiv, the attribute value is stored in a named String property; in a passthru div, the attribute name and value are set as properties of a TagAttribute instance and simply added to a List of arbitrary attributes. This is the reason that you can essentially set any attribute you want on a passthru element, whereas on a namespaced component you must adhere to the named properties (although, in recent versions, you can use the attrs property to define your own). In other words, at page load, it's actually slightly more work to construct a passthru div that has at least one attribute defined than it would be to construct an XspDiv with the same attribute / value mapping. When rendering, however, it's far less. Just for fun, let's compare the process for encoding a div with only an align attribute by pretending the HtmlDivRenderer is self-aware:

Passthru Div
I've started the open div tag, so now I need to send all the attribute values.
I'll loop through this internal List.
The first attribute is named align, and its value isn't null. So I'll send a space, the attribute name, an equals sign, a quote, the attribute value, and another quote. That was easy. Okay, on to the next.
Oh, that was the last list member. I'll send a > to end the open div tag.

XspDiv
I've started the open div tag, so now I need to send all the named property values.
I'll ask for the value of the align property. It's not null, so now let me check its length. It's greater than 0, so I need to send this attribute.
I'll send a space, the attribute name, an equals sign, a quote, the attribute value, and another quote. That was easy. Okay, on to the next.
I'll ask for the value of dir property. Oh, it's null. Okay, nothing to send.
Does lang have a value? Nope.
Okay, how about onclick? Nope.
ondblclick?
onkeydown?
I'm bored.
(10 more properties later...)
Well, does it at least have a title? Nope.
Phew, glad that's over with. Now I have to check for any arbitrary attributes, so I'll loop through this internal List.
Oh, the List is empty. I'll send a > to end the open div tag.

See the difference? The renderer actually has a lot more to do on a component with named properties than it does for a passthru element with arbitrary attributes simply because it has to ask for every possible property value and find out that it's null, rather than just looping through the properties that do have a value. Keep in mind, of course, that the difference between the two is likely around a millisecond each time, and the more attributes you specify on any given element, the less the differential between asking and just looping.

The second difference between how these types of components are rendered is that passthru elements cannot be manipulated via a theme. The UIPassthroughTag class does support the getStyleKitFamily method (which is how the theme determines the shared theme ID for each type of component), but it literally just returns null. Since this class does not include a getter or setter for the themeId property that most components share, there is no way to target any passthru element with a theme. When rendering any themable component, the renderer must consult the theme to determine if any property values are impacted based on the default theme ID for that type of component and any custom theme ID which may have been specified for this specific component instance. In contrast, all theme-related processing can simply be skipped for all elements with no namespace.

Neither of these differences are likely to impact performance enough for your users to notice, so in most cases, switching to passthru simply to gain performance benefits is going to be an unnecessary optimization. In apps where you're explicitly setting the page serialization and scope timeout options, and changing as many events as possible to use partial execution, because you're anticipating (or already encountering) a need for atypical speed or scalability, then you'd be well-served by switching to passthru for any elements that are purely structural and not behavioral - in other words, if you don't need to bind SSJS events to the onclick of a div, might as well just use passthru. But thanks to find and replace, it's much faster to remove the xp: from elements that don't need it after you've already gotten the page functional than it is to add it back in on those few elements where you do need the JSFish nature of a component.

Here's where you can achieve far more incremental performance gain: stop using panels and use divs instead - passthru or otherwise. The div component doesn't show up in the palette by default, so most developers just use a panel for every block element because it's readily accessible for dragging onto the page. Don't. Every time a panel is encoded, it checks for a component-specific ACL, and component-specific data sources. If either or both exist, then some other magic happens. For instance, if you've defined a data source local to a panel, the request scope (and, when the data source is initially created, the scope the data is associated with... which, by default, is the view scope for all data sources) must be updated to include pointers to the data source instance. At the end of encoding the panel, it has to check again for a local ACL or data sources so that it can clean up after itself... remove any scope pointers it added, and so forth. Compared to a div, a panel is a complicated beast. This differential is still small enough that your users won't notice unless the application is under heavy load, but this is simply a good habit to be in so that you don't have to suddenly change your behavior for one app that needs to be atypically scalable: unless a block element must be a panel, because you need localized data or security, just use a div. Besides, when you get in the habit of using the source tab instead of settling for the visual approximation of the design tab, it's less typing anyway.

Comments

Gravatar Image1 - "the difference between the two is likely around a millisecond each time"

Wrong prefix, my friend. Try "micro." Drop some tags inside a 100,000 element repeat and measure it.

This is not where application optimization is to be achieved.

Gravatar Image2 - In a test where a 10.000 element repeat creating 6 nested components each repeat I timed a difference of 100ms, compared to inline HTML. So yes, "micro". Will blog about it.

Gravatar Image3 - I knew you'd know what actually happens under the hood! It's interesting to know there are more properties available in R9, but I wonder whether best practice should be to use the align attribute or use CSS instead. I guess the former is only better if you're wanting to compute it, same as inline styling vs CSS.

I agree using XP divs is not that common and thanks for some mention somewhere a few years ago that pointed me in their direction. But scoping data and dataContexts to Panels is also probably underused. Further down the rabbit hole we go...!

Gravatar Image4 - Great point about the use of a panel when only a div is needed.

Post A Comment

:-D:-o:-p:-x:-(:-):-\:angry::cool::cry::emb::grin::huh::laugh::lips::rolleyes:;-)