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

« quick tip: persistent query string parameters | Main| they told me I could be anything »

the reason panel data sources can't be accessed outside the panel

Category xpages
Marky Roden recently called attention to a lesson he learned: if you associate a data source with a specific panel, you can only refer to it by name via events (or attributes) attached to components inside that panel. There are two very specific reasons for this:

  1. Hierarchical component processing
  2. The role the requestScope plays in variable resolution

While there are no doubt some subtle exceptions to this premise, you can consider all runtime processing of XPages to be hierarchical. To conceptualize this visually, imagine the view root (basically, the page itself) as the trunk of a tree. Think of each of the components that are placed directly on the page, rather than inside other components, as branches attached directly to the trunk of the tree. Each of those branches might, in turn, split off into several smaller branches.

Let's consider a simple real-world example:

Imagine an XPage that contains two 2x2 tables. In both tables, each row consists of two cells: one containing a label, and the second containing an editable field.

The server-side structure of an XPage at runtime is sometimes referred to as its "component tree": in this example, the XPage itself is the trunk of the tree. As we look up along the trunk, at some point the trunk splits into two branches: each branch is a table. This branch, in turn, splits into two separate branches, each of which is a table row. Each of these also splits into two branches, each of which is a table cell. The first of these two branches contains a single "leaf"; the leaf on the first branch is a label component. The leaf on the second branch is an input text.

So, if this were a real tree, and we wanted to reach any of those leaves (and, assuming these branches are too high for us to just reach up and touch a leaf), we would climb the trunk until we can reach the tables. We could then make our way along the branches - first one of the tables, then one of its rows, then one of its cells, until finally we're at a leaf. We've found our field. If we retreat back to the inner branches, and then repeat the process for all the rest, we would eventually touch each leaf.

To very briefly change the subject by introducing a less obvious analogy: imagine that some of these leaves (namely, those representing fields) are sentient, and your friend Steve can communicate telepathically them, so he constantly knows what they're thinking. But you can only communicate with them by touch. So… suppose that a leaf named Alan recently learned something you need to know. You decide to climb the tree and work your way along enough branches to eventually find Alan. This could take a while, because not only do most of these trees have a lot of branches, but you don't actually know what branch Alan is on. To find Alan, you have to follow each sub-hierarchy of branches, touching each leaf to ask it its name (technically, its ID).

Eventually, you'll ask a leaf, "are you Alan?", and it'll say yes. Now that you've found Alan, you can ask it everything it knows.

Meanwhile, Steve knew the whole time what Alan was thinking. He's standing safe on the ground, laughing at you scurrying from branch to branch and trying not to fall.

If any of my previous descriptions of why we should always call dataSource.getValue("propertyName") -- instead of getComponent("componentId").getValue() -- didn't make sense, perhaps this illustrates the premise a bit more clearly. getComponent() crawls along all the branches until it eventually finds Alan. dataSource is Steve. Just stay on the ground and ask Steve.

Okay, back to the topic at hand.

Part of why it's not a problem for the core processing of an XPage to traverse this hierarchical structure is that each of the various JSF lifecycle phases has to touch all the components anyway (with the exception, during some phases, of components that aren't rendered, and the descendants thereof), and each only needs to be touched once. So each phase starts at the view root (the <xp:view/> tag) and works its way inward until it's processed all of the components that it needs to a single time.

Some of the phases split the processing into "begin" and "end" operations for each component. For example, the bulk of the work of the RENDER_RESPONSE phase (the very last phase, which is what actually sends the HTML markup to the browser during a typical XPage request) consists of executing the following three methods on every rendered component in the tree:
  1. encodeBegin - For most components, the primary purpose of this method is to emit the opening HTML tag, along with all the currently applicable attributes.
  2. encodeChildren - Once the HTML tag for the current component has been sent, this method basically loops through all the components directly within the current component and passes control to them to "encode" themselves.
  3. encodeEnd - For many components, this just closes the HTML tag.
Because these methods execute in sequence, the result is hierarchical.

Let's consider an even simpler table than before: one row, one cell in that row, and one button in that cell:
  1. table runs encodeBegin, emitting an opening table tag with all pertinent attributes (client-side ID, CSS class, et cetera)
  2. encodeChildren passes control off to the table row
  3. encodeBegin of the row opens a tr tag
  4. encodeChildren passes control to the cell
  5. encodeBegin opens a td
  6. encodeChildren passes control to the button
  7. encodeBegin opens a button tag
  8. encodeChildren does nothing, because this button contains no child components
  9. encodeEnd of the button closes the button tag
  10. encodeEnd of the cell closes the td
  11. encodeEnd of the row closes the tr
  12. encodeEnd of the table closes the table
In reality, there's a bit more going on in there, like making sure there's a tbody surrounding any tr's, etc., but essentially the above is what occurs when this type of component hierarchy is rendered. When any components have multiple children, it gets more complex, except it's still really just these three methods making sure that a parent component gets to start sending its markup before any of its children are processed, that all children have been completely processed before it finishes sending its markup, and finally that it has an opportunity to finish processing itself before any sibling components are touched.

And that last premise is what causes the behavior Marky experienced. Panels are one of a few components that are considered "data containers". As such, when the above process starts for a panel, it doesn't just open a div tag... it also checks to see if any data sources (and/or data contexts) are associated with just that panel. If so, it goes through the motions of ensuring the data source exists, which consists of something like the following:
  1. Based on the scope (r/v/s/a) and name, does this already exist?
  2. If not, create one and store it in the correct scope
  3. Add a pointer in the requestScope
It's that last step that's crucial: adding anything to the requestScope makes it a variable.

Within any EL expression (whether standard Expression Language, SSJS, or some custom language), any symbols determined during expression parsing to be variables end up asking what's known as the "variable resolver" for the object to which that variable refers. There are several places the standard variable resolver looks, and one of them is the requestScope. So any requestScope key is a valid variable for as long as that key exists within the requestScope… which is usually the entire request.

Panels, however, don't just add variables, they clean up after themselves.* Specifically, at the end of a panel's processing, it again checks to see if it has its own data. If so, it removes the requestScope pointer it created during its initial processing. If the data source has a higher scope (the default is viewScope), the data source itself still exists within that scope. But there's no longer a pointer to it in the requestScope, so there's no longer a variable by which it can be directly accessed. On any subsequent requests, when the same panel is processed again, it will find the existing data source instance in its correct scope, create a fresh requestScope pointer, and then remove that pointer again when it finishes its processing.

That might seem a little pointless, since it's typically milli- (or even micro-) seconds between the creation and obliteration of the variable. But because all processing is hierarchical, this means that during all processing of components anywhere inside that panel, the requestScope pointer does exist. Any components in that sub-structure can refer to that variable. But once the panel and everything within it have been processed, that variable is gone. The more complex an XPage, the more valuable this automatic cleanup, because it optimizes resolution of all variables. So it's a good practice to always be looking for ways to structure our user interfaces in ways that allow us to localize our data as much as possible. This not only maximizes app performance, but also makes the application source more logically coherent by compartmentalizing its visual structure according to the conceptual structure of its data.

* P.S. If you considered the above and immediately guessed that Custom Controls and repeats behave similarly, you get a gold star.

Comments

Gravatar Image1 - Thanks for laying that out, Tim, very helpful.

Gravatar Image2 - we would climb the trunk until we can reach the tables. We could then make our way along the branches -

...and then, suddenly, the branch breaks and we fall down. We find ourselves on the ground, dazed. We have no clue what happened and find that we can no longer climb the tree. (Error 500: Command Not Handled)

Next week, we find that the cause was another gardener, planting a different tree, far away but using different brand of fertilizer incompatible with our tree.

Sorry, that analog just brought that instantly to my mind Emoticon Seriously, a great post!

Gravatar Image3 - That might explain why I had a problem recently with errors in InvokeApplication phase saying my dataContext "was not found", even though I wasn't getting errors in the dataContext code getting logged.

Gravatar Image4 - Almost certainly, Paul.

Gravatar Image5 - Marky, I'll get right on that once I finish the teleporter.

Jesse, that makes a lot of sense. Certainly much simpler than my explanation. Emoticon

Gravatar Image6 - If one of the table cells, in addition to two leaves, also has an acorn - what is the perceived distance from the XPage to the acorn should it happen to succumb to the forces of gravity and return to terra firma?

Gravatar Image7 - I guess I had always assumed (some of) this functionality at a subconscious level. For instance, that a data source would be bound to a custom control, the viewPanel control with its var (e.g.- rowData), or just a Panel data source was always relevant to only design inside of its part of the component tree (insert assumption of hierarchy, though it was never explicitly stated in my head).

This may have enlightened me in regards to how and why requestScope variables make sense. Thanks for help clearing up my cluttered brain! Time for a re-index.

Gravatar Image8 - I've always just conceptualized the XPage structure like a block of normal structured code in Java or otherwise. It's not a PERFECT match (since same-named variables can mess with each other), but it's served me well: panels are functions and repeats are loops. Just like you (hopefully) wouldn't think to access a function's or for-loop's variables from outside, I've never had the inclination to do the same with panels or repeats.

Gravatar Image9 - Thanks Tim - most enlightening :) The leaf analogy actually made sense as well believe it or not.

Now about that World Peace button which is still not working for me ;)