On Friday I picked up a query about how to pass a dominoDocument datasource to a custom control. It’s a question that’s been raised before but a definitive answer could not be located. It’s possibly because the answer is not specific to datasources, but any variable. In essence there is a simple rule to apply to this kind of query and also the reverse query, about accessing data outside of a repeat control.
The key pieces of understanding relate to compile-time and runtime:
- At compile time, no validation is performed around the variables used for the
value="#{myVar.myField}"data bindings. So if you bind to a variable that does not exist, the XPage or Custom Control still saves. - At runtime an XPage or Custom Control does not exist independently of anything else. They are all part of a single component tree that corresponds to the web page.
The key concept here is the component tree and how that is generated. Basically, the XPages runtime starts at the top level of the relevant XPage and builds a tree of each component.
It’s worth thinking of this like the source pane of the XPage, with the contents of any Custom Controls copied and pasted in place of the Custom Control references: what you get is a hierarchical tree with components that and siblings or children or deeper descendants. If loaded=”false”, they are still in the component tree. The only exception is a Dynamic Content Control, where only one facet’s contents are ever in the component tree (which is why that control is so powerful and important).
When you think of it this way, and put the two bullet points together, you start to realise that any variable (dataContext, dominoDocument data, dominoView data or any other kind of data, such as Object Data) is accessible to its descendants. So you don’t have to pass a dominoDocument datasource to a Custom Control: you can just reference the variable. As long as any XPage that uses that Custom Control has a datasource of a comparable type as a parent or ancestor of it, the reference will usually be found.¹ It will just find the closest ancestor variable of the same name.
So this also means if you have an XPage which has a dominoDocument datasource called “testDocument1”, then any Custom Control on that XPage can use value="#{testDocument1.Form}". Any Custom Control within that Custom Control can use value="#{testDocument1.Form}". And so on and so on.
Conversely, it’s not rocket science that if you add testDocument1 as a datasource to a Panel, you cannot access it outside the panel. If you want to, you need to move it to an area that’s an ancestor of where you want to use it. The logic also applies to Repeat Controls: its variable cannot be accessed outside the Repeat Control. You can move the collection – the value property of the Repeat Control – higher up the component tree, to an ancestor of something outside the Repeat Control. But each element – the var of the Repeat Control – cannot be moved outside the Repeat Control, so cannot be accessed by a sibling or parent of the Repeat Control.
¹ Contents of Repeat Controls are a key caveat here, as encountered on a recent StackOverflow question. It’s worth adding that this understanding is theoretical and I’ve not fully investigated all the possible scenarios, so it’s something to bear in mind and troubleshoot rather than definitive “this is how you should code it”. A Repeat Control has a repeatControls property whose default value is “false”. So unless it’s explicitly set, it’s also “false”. That means that the component tree should create a single “abstract” instance of each control within the repeat, which is not bound to an actual row of data. This means the value property of the repeat is resolved, but the var is not resolved until rendering the contents. This can have an impact if there are nested repeat controls that are needed before Render Response phase, such as editable controls. I’ve also seen it with contents in the detail facet of a Data View control.
For a Repeat Control, you may be able to set repeatControls="true". This means that it should have the contents of the repeat replicated in the component tree, one instance for each “row” in the repeat. It effectively means creating a row bound at page load to a row in the repeat, so nested components can be bound to a lower level. But because it’s being bound at page load, a partial refresh will not change which element of the collection each set of controls is bound to – basically, paging will not work.
There’s also no way to set repeatControls="true" on a Data View.
The alternative to both scenarios, as I mentioned in the StackOverflow question, is to check the current phase being run and abort your calculation accordingly.