Saturday, October 18, 2008

How does WOSwitchComponent actually work again?

(from the mailing list, but it really should have been a blog post :) )

jad WOSwitchComponent and walk through and explain to yourself why WOSwitchComponent doesn't show the same instance of a component for every session in your app, particularly given that there will only be one instance of a WOSwitchComponent on a given parsed page (meaning that if there's a WOSwitchComponent for an element in your Main component, there will only be ONE INSTANCE of WOSwitchComponent servicing that slot on the page for all of your users). Prepare to get a much deeper understanding and appreciation of how WO works and how clever the original authors were.

So the magic is WOComponentReference + WOComponentDefinition + WOComponent. What happens is that when your template is parsed, it gets parsed into a bunch of WOComponentReferences. If you understand WOAssociation, WOComponentReference is like the equivalent concept for components. That is, you have a handle that says "I represent an AjaxInPlace with a set of bindings" but it's not a stateful instance, it just describes how you're going to use it.

During parsing an entire tree of these is built up. It represents your parsed template without state. The sort of interesting part is that these things actually extend WODynamicElement, so they LOOK like components from the outside.

What happens is, and this is the kind of neat and clever part, is that at runtime, you say pageWithName("MyPage"). You get a new instance of the top level component. For each parsed element on the page (a WOComponentReference), the framework will walk down the tree and say "give me a stateful peer for this WOComponentReference for a component of this name, and use this set of bindings". It's here, btw, that it can decide whether subcomponents get new instances or come from the shared pool. Then the part that I DIDN'T know about -- the way these are connected is that the new instance of a child component is pushed into a dictionary in the WOComponent parent keyed off of element ID, so the framework can walk down the parsed tree and say to the parent, "give me your child component with the elementID x". If this component has already been materialized, the component will hand back a subcomponent from its dictionary. If one isn't there, it will make a new one and push it into the dictionary. So this starts to make sense, now, why you always get a new top level component instance -- it's the root of the state tree. There has to be a root node to hang all these stateful components off of. The magic method for all of this is actually WOComponentDefinition._componentInstanceInContext(context), btw.

So fundamentally, there's this stateless tree of elements that represents my entire parsed page and there's this stateful tree of WOComponent instances that represent the actual components of the page. So in normal components, the structure of your page is basically predetermined -- you have a bunch of WOConditionals, WOStrings, AjaxInPlace's, etc, all in a tree and page state determines which path through this graph you take. WOSwitchComponent, though, is a crazy twist on this. WOSwitchComponent DYNAMICALLY determines its page structure -- that is, the "parsed side" is indeterminate for a WOSwitchComponent. And if you look at WOSwitchComponent, this is exactly what it's doing. When you ask for a "SomeComponent", the cache doesn't return to you a WOComponent of the class you asked for, but instead a WOComponentReference -- those stateless handles. It's clever as hell. When it needs the component to work on, it looks up the correct instance of WOComponent based on the reference that it has cached (that is shared by everyone!) by asking the context().component() for a child with the current elementID, and will make one if it doesn't exist. So WOSwitchComponent takes advantage of this peer concept and basically builds this structure up itself on-the-fly. When this clicked for me, it was a "woah .. that's really slick" moment.

All of this finagling on my part was because I wanted to implement a new type of component that would allow you to, for instance, create a component on your page that would act like an "embedded page," where instead of having the result of invokeAction replace the entire page, it would instead just replace the embedded page component. To do this, you have to muck with page structure on-the-fly, which is pretty similar to what WOSwitchComponent does, but changing over time. So I just pulled up WOSwitchComponent and thought "I'll just base my code on this - -should be pretty easy" except that I looked at WOSwitchComponent and decided that it's impossible that it actually worked right :) But it does, and I began my quest to find out how :)

Incidentally, I THINK (haven't verified yet) that the third parameter to the constructor of a WODynamicElement, which I've always sort of wondered its exact purpose, is your stateless-parsed-element-peer. So you're being handed the reference to your stateless equivalent in the parse tree. This is my theory, at least. This gives you access to the structure of your children components, which during the R-R loop can also be used to access the stateful peers, etc.

So anyway ... That's the long story that probably nobody else cares about, but I think it's sort of a fascinating aspect of WO, because it's this crazy and clever underpinning that makes the entire framework function that I really had no idea about the inner workings of.


Q said...

Very cool.

EvilTofu said...

You can download the WebObjects source code if you have an Apple developer account. I think.

Mike Schrag said...

Unfortunately WO is not open source. Only nightly downloads are available on ADC. However, WO DOES decompile pretty clearly using jad.