Trouble chaining Javascript module calls in Athena (example code attached).
Hi all. I'm doing a lot of Athena stuff at the moment. Calls from client->server and server-> client (which is virtually 100% of the calls) are working wonderfully. However, I'm having difficulty getting one Javascript module to call directly onto another Javascript module (that is, client->client) in a way that I would expect, and I think it's something in the package/module system Athena uses that I haven't yet grokked. I've attached a minimal test case. Since it actually has a directory structure, I've tar-gzipped it; sorry for the additional inconvenience. Directions are in the README file. The only requirement is that you modify PYTHONPATH before twistd-ing the tac file (actually, it's a .py file, but it should be a .tac file). Briefly, the problem is this: I've got two Javascript modules associated with LiveElements; in the example these are XCall.A and XCall.B. XCall.A knows about XCall.B through an import "statement"; that is, it has a line: // import XCall.B It tries to call a method on XCall.B (XCall.B.chain()), but it can't see this method and throws an exception (which is caught by Nevow). Examining the Javascript objects through Firebug shows that XCall.B doesn't have any of the methods passed to its methods() method in the object top-level namespace; instead, these are all available in the "prototype" attribute. In other words, I can call XCall.B.prototype.chain(), but not XCall.B.chain(). Now, I know that the "prototype" attribute is the kooky way that Javascript implements some of its OO-ness, but all the Nevow code seems not to do it this way: it just imports and uses directly: // import Foo.Bar Foo.Bar.doStuff() so I'm confused as to why I can't do the same thing myself. I'm running Python 2.5, Twisted 2.5.0, and have the same problem on both Nevow 0.9.18 and on trunk head. Can anyone spot what I'm doing wrong? Thanks, Ricky
On Wed, 30 May 2007 01:44:07 +0300, kgi <iacovou@gmail.com> wrote:
Hi all.
I'm doing a lot of Athena stuff at the moment. Calls from client->server and server-> client (which is virtually 100% of the calls) are working wonderfully. However, I'm having difficulty getting one Javascript module to call directly onto another Javascript module (that is, client->client) in a way that I would expect, and I think it's something in the package/module system Athena uses that I haven't yet grokked.
I've attached a minimal test case. Since it actually has a directory structure, I've tar-gzipped it; sorry for the additional inconvenience. Directions are in the README file. The only requirement is that you modify PYTHONPATH before twistd-ing the tac file (actually, it's a .py file, but it should be a .tac file).
Briefly, the problem is this: I've got two Javascript modules associated with LiveElements; in the example these are XCall.A and XCall.B. XCall.A knows about XCall.B through an import "statement"; that is, it has a line:
// import XCall.B
It tries to call a method on XCall.B (XCall.B.chain()), but it can't see this method and throws an exception (which is caught by Nevow).
This is like trying to call an unbound method in Python. What you really want to be doing is calling chain on an /instance/ of XCall.B, not on the class object itself. Also, I'd recommend not clobbering XCall.A and XCall.B with your class objects, since those are set up to be module objects because they each have a corresponding .js file. Athena's module system tries to mimick many aspects of Python's - in this case, XCall corresponds to a package, XCall.A and XCall.B are modules in that package, and you want to be defining things like XCall.A.Foo and XCall.B.Bar as attributes of those modules. Jean-Paul
On Wednesday 30 May 2007 02:25:23 Jean-Paul Calderone wrote:
On Wed, 30 May 2007 01:44:07 +0300, kgi <iacovou@gmail.com> wrote:
Hi Jean-Paul; thanks yet again for chiming in with a timely response.
This is like trying to call an unbound method in Python. What you really want to be doing is calling chain on an /instance/ of XCall.B, not on the class object itself.
I see what you mean. I *thought* I was just mimicking the approach used in lots of the Nevow code itself, but now I see The Error Of My Ways. Just to make sure that my insight is not bogus, can you comment on the "truthiness" of the following statements: 1. When a LiveElement is rendered, it is assigned an Athena Id (probably server-side) and something in the Nevow runtime creates a class *instance*, identifiable and accessible from the server through the magic of the Athena Id. In other words, there is rarely (never?) direct instantiation in JS code of objects ("var f = new Foo()") when the classes are Athena subclasses. 2. If I instantiated N LiveElements of the same class, all N would be individually accessible from the server just by calling object.callRemote(). 3. However, whereas server->client calls have implicit access to the Athena Id, so the callRemote() can be routed accordingly to the right instance, client-side Javascript doesn't have this. I think I was confused by the fact that up until now I've only been having one LiveElement instance of each type on a page, so I never had to think about object disambiguation. If this is the case, then I need a way of informing *client* side objects how to find each other. In other words, I need to embed the callee javascript object's Athena Id into the caller, either at creation time, or later. Is this a "done thing"? (Either that, or I go via the server)
Also, I'd recommend not clobbering XCall.A and XCall.B with your class objects, since those are set up to be module objects because they each have a corresponding .js file. Athena's module system tries to mimick many aspects of Python's - in this case, XCall corresponds to a package, XCall.A and XCall.B are modules in that package, and you want to be defining things like XCall.A.Foo and XCall.B.Bar as attributes of those modules.
Ah, I see. You mean that the very existence of the files XCall/A.js sets up the (empty?) XCall.A namespace, and within XCall.A.js I define classes like: XCall.A.Foo = Nevow.Athena.Widget.subclass ( 'XCall.A.Foo' ); Thanks again for your help, Jean-Paul, I know how busy you guys are. I'm sorry if it takes two or three attempts to get my head round some of the muddier aspects of how it all fits together - it's all pretty new and unfamiliar. Regards, Ricky
On Wed, 30 May 2007 12:58:48 +0300, kgi <iacovou@gmail.com> wrote:
On Wednesday 30 May 2007 02:25:23 Jean-Paul Calderone wrote:
On Wed, 30 May 2007 01:44:07 +0300, kgi <iacovou@gmail.com> wrote:
Hi Jean-Paul; thanks yet again for chiming in with a timely response.
This is like trying to call an unbound method in Python. What you really want to be doing is calling chain on an /instance/ of XCall.B, not on the class object itself.
I see what you mean. I *thought* I was just mimicking the approach used in lots of the Nevow code itself, but now I see The Error Of My Ways. Just to make sure that my insight is not bogus, can you comment on the "truthiness" of the following statements:
Sure.
1. When a LiveElement is rendered, it is assigned an Athena Id (probably server-side) and something in the Nevow runtime creates a class *instance*, identifiable and accessible from the server through the magic of the Athena Id. In other words, there is rarely (never?) direct instantiation in JS code of objects ("var f = new Foo()") when the classes are Athena subclasses.
Yep. To be specific, "Nevow.Athena.Widget subclasses", though I don't think there are any other interested classes in the Nevow.Athena namespace. ;)
2. If I instantiated N LiveElements of the same class, all N would be individually accessible from the server just by calling object.callRemote().
Right. Each server-side LiveElement instance is tangled up with its corresponding client-side Nevow.Athena.Widget (subclass) instance, and vice versa.
3. However, whereas server->client calls have implicit access to the Athena Id, so the callRemote() can be routed accordingly to the right instance, client-side Javascript doesn't have this.
Roughly correct, but see below.
I think I was confused by the fact that up until now I've only been having one LiveElement instance of each type on a page, so I never had to think about object disambiguation.
If this is the case, then I need a way of informing *client* side objects how to find each other. In other words, I need to embed the callee javascript object's Athena Id into the caller, either at creation time, or later. Is this a "done thing"?
(Either that, or I go via the server)
Widgets on the client have a hierarchical relationship with each other which reflects the hierarchical relationship of their corresponding server-side LiveElement progenitors. That is, if LiveElement B has setFragmentParent called on it with LiveElement A as an argument, then Widget B will have a widgetParent property which refers to Widget A; similarly, Widget A's childWidgets array property will include widget B. These relationships can be used to pass messages between widgets in the browser without involving the server.
Also, I'd recommend not clobbering XCall.A and XCall.B with your class objects, since those are set up to be module objects because they each have a corresponding .js file. Athena's module system tries to mimick many aspects of Python's - in this case, XCall corresponds to a package, XCall.A and XCall.B are modules in that package, and you want to be defining things like XCall.A.Foo and XCall.B.Bar as attributes of those modules.
Ah, I see. You mean that the very existence of the files XCall/A.js sets up the (empty?) XCall.A namespace, and within XCall.A.js I define classes like:
XCall.A.Foo = Nevow.Athena.Widget.subclass ( 'XCall.A.Foo' );
Yep.
Thanks again for your help, Jean-Paul, I know how busy you guys are. I'm sorry if it takes two or three attempts to get my head round some of the muddier aspects of how it all fits together - it's all pretty new and unfamiliar.
No problem. :) Hope this helps, Jean-Paul
On Wednesday 30 May 2007 15:15:56 Jean-Paul Calderone wrote: Hi Jean-Paul; thanks for your email - very helpful.
Widgets on the client have a hierarchical relationship with each other which reflects the hierarchical relationship of their corresponding server-side LiveElement progenitors. That is, if LiveElement B has setFragmentParent called on it with LiveElement A as an argument, then Widget B will have a widgetParent property which refers to Widget A; similarly, Widget A's childWidgets array property will include widget B. These relationships can be used to pass messages between widgets in the browser without involving the server.
Yep, I see the attributes for traversing along the hierarchy. Unless I've misunderstood, in order for a widget's Javascript to be able to call another widget's Javascript, it has to do something like: self.widgetParent.widgetParent.widgetParent ... childWidgets[0].childWidgets[1].doStuff() This feels somewhat wrong, partly because of fragility: if I move the location of a widget in the hierarchy, its Javascript code will need updating. Is there a way of accessing another widget in the page directly by name? An example might be a panel where I want any widget to be able to append status messages. I would imagine this might entail assigning a user-controlled widget id to the top-level DOM node of a LiveElement (as opposed to *subnodes* of the widget, in which ids are rewritten for use by nodeById(), and which need the athena id to access). I suppose the stan would look like this (this doesn't work, because the id gets clobbered by the autogenerated id): T.div ( _id = 'my-unique-id', render = T.directive ( "mywidget" ) ), T.div ( _id = 'another-unique-id', render = T.directive ( "mywidget" ) ) Then in JS code of another widget: w = self.getAthenaWidgetByName ( 'my-unique-id' ); w.doStuff() Obviously, the LiveElement nodes are already all assigned a unique id, it's just that it's done automatically and there's no obvious way of getting at them ahead-of-time. I guess I could always cheat and wrap all LiveElements inside named divs and do document.getElementById(), but that feels silly, given that the divs are already named. There are plenty of methods in Nevow/nevow/js/Nevow/Athena/__init__.js for getting hold of things but they all seem to need either the *numeric* athena id (fromAthenaID, callByAthenaID) or a reference to a node (athenaIDFromNode, athenaClassFromNode). Ricky
On Wed, 30 May 2007 17:53:11 +0300, kgi <iacovou@gmail.com> wrote:
On Wednesday 30 May 2007 15:15:56 Jean-Paul Calderone wrote:
Hi Jean-Paul; thanks for your email - very helpful.
Widgets on the client have a hierarchical relationship with each other which reflects the hierarchical relationship of their corresponding server-side LiveElement progenitors. That is, if LiveElement B has setFragmentParent called on it with LiveElement A as an argument, then Widget B will have a widgetParent property which refers to Widget A; similarly, Widget A's childWidgets array property will include widget B. These relationships can be used to pass messages between widgets in the browser without involving the server.
Yep, I see the attributes for traversing along the hierarchy. Unless I've misunderstood, in order for a widget's Javascript to be able to call another widget's Javascript, it has to do something like:
self.widgetParent.widgetParent.widgetParent ... childWidgets[0].childWidgets[1].doStuff()
This feels somewhat wrong, partly because of fragility: if I move the location of a widget in the hierarchy, its Javascript code will need updating.
Yep. One solution to this is to do something like: self.widgetParent.doStuffToSpecificThing() Supplying an implementation of doStuffToSpecific for whatever the widget's parent happens to be which knows about its position in the overall page layout. This keeps the inter-widget dependency limited to a single step.
Is there a way of accessing another widget in the page directly by name? An example might be a panel where I want any widget to be able to append status messages.
I would imagine this might entail assigning a user-controlled widget id to the top-level DOM node of a LiveElement (as opposed to *subnodes* of the widget, in which ids are rewritten for use by nodeById(), and which need the athena id to access).
I suppose the stan would look like this (this doesn't work, because the id gets clobbered by the autogenerated id):
T.div ( _id = 'my-unique-id', render = T.directive ( "mywidget" ) ), T.div ( _id = 'another-unique-id', render = T.directive ( "mywidget" ) )
Then in JS code of another widget:
w = self.getAthenaWidgetByName ( 'my-unique-id' ); w.doStuff()
Obviously, the LiveElement nodes are already all assigned a unique id, it's just that it's done automatically and there's no obvious way of getting at them ahead-of-time.
I guess I could always cheat and wrap all LiveElements inside named divs and do document.getElementById(), but that feels silly, given that the divs are already named.
There are plenty of methods in Nevow/nevow/js/Nevow/Athena/__init__.js for getting hold of things but they all seem to need either the *numeric* athena id (fromAthenaID, callByAthenaID) or a reference to a node (athenaIDFromNode, athenaClassFromNode).
Something about this approach rubs me the wrong way. Identifiers which are required to be globally unique sends one down the path of being limited to only supporting certain page configurations. Maybe if there were some well defined algorithm for choosing the "nearest" widget with a given identifier, something could be worked out. I'm not too sure about this either, though. You can already do something like this, though, if the class name is a sufficient identifier for the widget you want (find the node with the right value for athena:class, then find the widget associated with the node). I tend to discourage this approach in favor of the one I gave at the top of the message, though. Jean-Paul
On Wednesday 30 May 2007 18:12:32 Jean-Paul Calderone wrote:
Something about this approach rubs me the wrong way. Identifiers which are required to be globally unique sends one down the path of being limited to only supporting certain page configurations. Maybe if there were some well defined algorithm for choosing the "nearest" widget with a given identifier, something could be worked out. I'm not too sure about this either, though.
Is it the resultant uniqueness or the interdependency that worries you? I can see why this would be icky if the unique widget name were somehow hard-coded into the LiveElement class definition (meaning you could only create one such widget) but I don't see how it would be in any way harmful or limiting if the unique widget identifier were specified at widget construction time. Some things really do need to exist exactly once; no-one balks at putting '<div id="navigation">' in a web page (it would be confusing to have two navigation panels). I suppose that if you're trying to write 100% re-usable widgets then having cross-widget dependencies is a bad idea, but that's a problem that affects all the techniques described here (except for Daniel's publish/subscribe approach). It looks like boshing something into the parent namespace in the widget's __init__ method is the simplest approach, but of course it's also the evillest approach as it does indeed limit you to one such widget. Anyway, I'll have a think about the various options; thanks for your input! Ricky
Pardon me for jumping in here, but I think you're making this too hard. On Wed, 30 May 2007 09:53:11 -0500, kgi <iacovou@gmail.com> wrote:
Yep, I see the attributes for traversing along the hierarchy. Unless I've misunderstood, in order for a widget's Javascript to be able to call another widget's Javascript, it has to do something like:
self.widgetParent.widgetParent.widgetParent ... childWidgets[0].childWidgets[1].doStuff()
You can certainly do that, but there are plenty of other means of accomplishing this aim. For example, a widget has __init__() and nodeInserted() methods that you can override. If you want your widget instances accessible by some name known to you, then just do: self.widgetParent.someNameILikeToUseForThisParticularWidget = self; Better still, use a client-side event dispatching mechanism, and just let interested widget subscribe to a given "channel": MyDispatcher = { _subscribers: [], publish: function(channel, message){ ... }, subscribe: function(channel){ } ... }; Hopefully, this gives you some easier options to consider. -- L. Daniel Burr ldanielburr@mac.com
participants (3)
-
Jean-Paul Calderone
-
kgi
-
L. Daniel Burr