Quick Questions about JS mapping in Athena.
Hi all. I have a couple of quick questions regarding JS in Athena. At least, they're quick to ask; I hope they're also quick to answer. First of all, it looks like the mapping of package/module to JS source file "ought" to be contained in a plugin. This is described in the Athena page, and (almost) mandated by the fact that athena.py's allJavascriptPackages() performs a plugin search like this: from nevow import plugins [...] d = {} for p in plugin.getPlugIns(inevow.IJavascriptPackage, plugins): d.update(p.mapping) return d ... which uses the fact that a module passed to getPlugins() will use that as the search path, so this will in fact search nevow/plugins/, and that's hard-coded. However, the story doesn't end there: nevow/plugins/__init__.py contains the line: __path__ = [os.path.abspath(os.path.join(x, 'nevow', 'plugins')) for x in sys.path] This means that if I look for modules under nevow/plugins, I will magically pick up any module that has been put under a nevow/plugins directory in my sys.path. (Innocent onlookers can see the bottom of http://docs.python.org/tut/node8.html for the details; I didn't know this was possible until I saw this method being mentioned in the twisted plugin docs at http://twistedmatrix.com/projects/core/documentation/howto/plugin.html). Now this is quite clever, but if I've understood the mechanism right, it's not necessarily all that handy, because it dictates either the location of particular directories within one's project (i.e. my athena code is not at the top-level of my code tree, so I'd prefer not to have 'nevow/plugins' at the top-level for getPlugins() to pick up via sys.path) or an unnecessary entry into sys.path. So, my questions is: is there a way to register the JS mapping elsewhere? It looks like modifying self.jsModules.mapping directly inside the constructor of a LivePage might do it, but that feels hacky as heck. As an aside: I desperately don't want to ruffle any feathers here, but I also feel that using the twisted plugins system to store what amounts to a dictionary is a bit of a sledgehammer and nut thing. Since the actual LiveElement classes don't even live in the same place as the mapping, it's not as if the athena.JSPackages are really "plugins", as they only contain data, not code. Wouldn't having a "jsFile" attribute directly on the LiveElements be more natural, since in any case, the renderer looks up the "jsClass" attribute and looks up the corresponding JS file in jsModules? Possibly in conjunction with an attribute (allElements?) on LivePage that you can set to pre-declare all Elements you intend to use ahead of time so all the JS gets included in the page in the same place). Secondly, what are the requirements on the naming of JS packages? Many of the examples use a dotted notation ("Foo.Bar"), but not all. Is there any advantage on the JS side to having multiple modules share a top-level module name (memory usage, runtime speed)? Are the strings significant at all? As far as I can tell, the keys in the jsModules are arbitrary and just need to match the jsClass declarations in the LiveElements and the "// import" declarations in JS source files. Am I right in believing that there's no requirement that they match the class instances actually used in the JS source files? Anyway, that's my two questions and an aside; thanks for your patience, and thanks for any help you can afford. Regards, Ricky
On Thu, 17 May 2007 17:57:35 +0300, kgi <iacovou@gmail.com> wrote:
Hi all.
I have a couple of quick questions regarding JS in Athena. At least, they're quick to ask; I hope they're also quick to answer.
[snip]
So, my questions is: is there a way to register the JS mapping elsewhere? It looks like modifying self.jsModules.mapping directly inside the constructor of a LivePage might do it, but that feels hacky as heck.
This works, but I agree it has a hackish feel.
As an aside: I desperately don't want to ruffle any feathers here, but I also feel that using the twisted plugins system to store what amounts to a dictionary is a bit of a sledgehammer and nut thing. Since the actual LiveElement classes don't even live in the same place as the mapping, it's not as if the athena.JSPackages are really "plugins", as they only contain data, not code. Wouldn't having a "jsFile" attribute directly on the LiveElements be more natural, since in any case, the renderer looks up the "jsClass" attribute and looks up the corresponding JS file in jsModules? Possibly in conjunction with an attribute (allElements?) on LivePage that you can set to pre-declare all Elements you intend to use ahead of time so all the JS gets included in the page in the same place).
The import system requires more or less complete knowledge of what modules are defined in order to resolve dependencies. If each LiveElement took care to declaring which file it needed, then the page would need to be rendered completely before Athena could figure out which modules needed to be loaded, by which time it is a little too late to include them in the page (perhaps this could be made to work, but it would be a hassle). I can sympthatize somewhat with your desire to avoid conforming to the structure required by the Twisted plugin system here, but I don't have a better solution to suggest now. In my copious spare time, I am trying to push something better into Twisted (plugins are really a low level thing, not what you actually want to be interacting with), but that's not likely to see any results for quite a while to come. I wouldn't be happy to see a solution that required declaring all LiveElement classes a LivePage is going to use, since my own most common use-case for LivePages involves my not having any idea what most of the LiveElements will be (and in fact, changing what they will be with almost every render). I'm open to other suggestions and discussion about improving this area of Athena, though.
Secondly, what are the requirements on the naming of JS packages? Many of the examples use a dotted notation ("Foo.Bar"), but not all. Is there any advantage on the JS side to having multiple modules share a top-level module name (memory usage, runtime speed)? Are the strings significant at all? As far as I can tell, the keys in the jsModules are arbitrary and just need to match the jsClass declarations in the LiveElements and the "// import" declarations in JS source files. Am I right in believing that there's no requirement that they match the class instances actually used in the JS source files?
The idea is to provide a package hierarchy, as Python has. The strings will be split on "." in a couple places, but if there is simply no "." in them, this will work (since it is equivalent to any top-level name in an arrangement which does use dotted names). The goal of dotted names is to avoid polluting the top-level namespace more than necessary. For sanity's sake, I would define classes in a module as attributes of that module, rather than using arbitrary other names, but there's little or nothing Athena can actually do to prevent this, and as long as you aren't clobbering any names which Athena itself uses (and almost all such names are beneath the Divmod or Nevow namespace), it probably won't do anything to interfer with Athena.
Anyway, that's my two questions and an aside; thanks for your patience, and thanks for any help you can afford.
Hope this helps, Jean-Paul
On Thursday 17 May 2007 18:31:59 Jean-Paul Calderone wrote: Hi Jean-Paul; thanks for your quick answer.
The import system requires more or less complete knowledge of what modules are defined in order to resolve dependencies. If each LiveElement took care to declaring which file it needed, then the page would need to be rendered completely before Athena could figure out which modules needed to be loaded, by which time it is a little too late to include them in the page (perhaps this could be made to work, but it would be a hassle).
OK, I guess I'll just have to bite the bullet. Adding something to sys.path is not such a big deal.
I wouldn't be happy to see a solution that required declaring all LiveElement classes a LivePage is going to use, since my own most common use-case for LivePages involves my not having any idea what most of the LiveElements will be (and in fact, changing what they will be with almost every render). I'm open to other suggestions and discussion about improving this area of Athena, though.
Oh, really? That's interesting. How does Athena push new Javascript code to a page after the initial render? I didn't even know it was possible to do such a thing: for me, Javascript has always been something you link to from the page header. Looks like my 4th Edition "JS Definitive Guide" (2001) is showing its age :-)
The idea is to provide a package hierarchy, as Python has. The strings will be split on "." in a couple places, but if there is simply no "." in them, this will work (since it is equivalent to any top-level name in an arrangement which does use dotted names). The goal of dotted names is to avoid polluting the top-level namespace more than necessary.
You're talking about Javascript's top-level namespace, right? (I assume these strings never go anywhere near Python's namespace). OK, that sounds logical; I was just wondering whether there was anything more to it that.
For sanity's sake, I would define classes in a module as attributes of that module, rather than using arbitrary other names
Hmm, I'm not quite sure I understand what you mean in this context. I *think* you're talking about JS classes, but the terminology is Python terminology. If the former (JS classes) do you mean, you'd prefer to have a single (or a few) javascript file(s) and have these define the JS for *lots* of related LiveElement classes, which all declare themselves to be a (namespaced) jsClass, and all these jsClasses map to the same physical JS file? In other words, for project "Cutlery": # In Python LivePage stuff: class Fork ( LiveElement ): jsClass = u'Cutlery.Fork' class Spoon ( LiveElement ): jsClass = u'Cutlery.Spoon' class Knife ( LiveElement ): jsClass = u'Cutlery.Knife' # In Python plugin code: jsModules.mapping = { # Note: the same file! u'Cutlery.Fork' : 'cutlery.js', u'Cutlery.Spoon' : 'cutlery.js', u'Cutlery.Knife' : 'cutlery.js', } # In the file cutlery.js: Cutlery = {}; Cutlery.Fork = Nevow.Athena.Widget.subclass ( 'Cutlery.Fork' ); Cutlery.Fork.methods ( ... ); Cutlery.Spoon = Nevow.Athena.Widget.subclass ( 'Cutlery.Spoon' ); Cutlery.Spoon.methods ( ... ); Cutlery.Knife = Nevow.Athena.Widget.subclass ( 'Cutlery.Knife' ); Cutlery.Knife.methods ( ... ); Is this what you mean? Actually, I have a couple more very quick questions that I thought up while typing this: - Am I right in thinking that no athena JS files ever need to have declare any of the modules mentioned in BOOTSTRAP_MODULES (e.g. Divmod, Nevow.Athena)? - Is there a recommended way for LiveElements to access each other on the server? For example, I click on an element of (client-side) Foo that invokes a method on (server-side) Foo; this then needs to update the state of (server-side) Bar, which needs to update (client-side) Bar. At the moment I'm using an manual ad-hoc registry, with the occasional (naughty?) obj.fragmentParent thrown in. I was wondering if there was a more robust method already in place that I had totally missed (like the LiveElements being assigned names upon creation, which can then be automagically found through something like page.findElementByName ( "Bar" )). Regards, Ricky
On Thu, 17 May 2007 19:29:26 +0300, kgi <iacovou@gmail.com> wrote:
On Thursday 17 May 2007 18:31:59 Jean-Paul Calderone wrote:
Hi Jean-Paul; thanks for your quick answer.
The import system requires more or less complete knowledge of what modules are defined in order to resolve dependencies. If each LiveElement took care to declaring which file it needed, then the page would need to be rendered completely before Athena could figure out which modules needed to be loaded, by which time it is a little too late to include them in the page (perhaps this could be made to work, but it would be a hassle).
OK, I guess I'll just have to bite the bullet. Adding something to sys.path is not such a big deal.
I wouldn't be happy to see a solution that required declaring all LiveElement classes a LivePage is going to use, since my own most common use-case for LivePages involves my not having any idea what most of the LiveElements will be (and in fact, changing what they will be with almost every render). I'm open to other suggestions and discussion about improving this area of Athena, though.
Oh, really? That's interesting. How does Athena push new Javascript code to a page after the initial render? I didn't even know it was possible to do such a thing: for me, Javascript has always been something you link to from the page header. Looks like my 4th Edition "JS Definitive Guide" (2001) is showing its age :-)
Basically it just notices when some required javascript is absent, loads it from the server, and eval()'s it. The same dependency system is used to resolve dependencies at page render time (which are turned into <script> tags in the <head> of the rendered page) and dynamically after the initial page render (which are turned into these dynamic load/evals).
The idea is to provide a package hierarchy, as Python has. The strings will be split on "." in a couple places, but if there is simply no "." in them, this will work (since it is equivalent to any top-level name in an arrangement which does use dotted names). The goal of dotted names is to avoid polluting the top-level namespace more than necessary.
You're talking about Javascript's top-level namespace, right? (I assume these strings never go anywhere near Python's namespace). OK, that sounds logical; I was just wondering whether there was anything more to it that.
Yep.
For sanity's sake, I would define classes in a module as attributes of that module, rather than using arbitrary other names
Hmm, I'm not quite sure I understand what you mean in this context. I *think* you're talking about JS classes, but the terminology is Python terminology.
Yep. Python has a pretty reasonable module system. Athena's JS "module system" is loosely based on it, so a lot of Python terminology makes sense for it as well.
If the former (JS classes) do you mean, you'd prefer to have a single (or a few) javascript file(s) and have these define the JS for *lots* of related LiveElement classes, which all declare themselves to be a (namespaced) jsClass, and all these jsClasses map to the same physical JS file?
I didn't mean to imply anything about how much or little JS is in any single source file. Mostly I just wanted to point out that I think a single source file should completely define any particular module. ie, if a.js puts things onto the JS "module" A, b.js should _not_ put things onto it.
In other words, for project "Cutlery":
# In Python LivePage stuff:
class Fork ( LiveElement ): jsClass = u'Cutlery.Fork'
class Spoon ( LiveElement ): jsClass = u'Cutlery.Spoon'
class Knife ( LiveElement ): jsClass = u'Cutlery.Knife'
# In Python plugin code:
jsModules.mapping = { # Note: the same file! u'Cutlery.Fork' : 'cutlery.js', u'Cutlery.Spoon' : 'cutlery.js', u'Cutlery.Knife' : 'cutlery.js', }
Note that since the dependency resolution system works upwards, this mapping only needs one entry for this example to work: jsModules.mapping = {u'Cutlery': 'cutlery.js'}
# In the file cutlery.js:
Cutlery = {};
Cutlery.Fork = Nevow.Athena.Widget.subclass ( 'Cutlery.Fork' ); Cutlery.Fork.methods ( ... );
Cutlery.Spoon = Nevow.Athena.Widget.subclass ( 'Cutlery.Spoon' ); Cutlery.Spoon.methods ( ... );
Cutlery.Knife = Nevow.Athena.Widget.subclass ( 'Cutlery.Knife' ); Cutlery.Knife.methods ( ... );
Is this what you mean?
I... think? so... I'm not sure if you were trying to demonstrate something by putting the jsModules mapping into the same file as the rest of the Python (since you pointed it out, I suspect you were, but I'm not sure what).
Actually, I have a couple more very quick questions that I thought up while typing this:
- Am I right in thinking that no athena JS files ever need to have declare any of the modules mentioned in BOOTSTRAP_MODULES (e.g. Divmod, Nevow.Athena)?
Well, it'll work if they don't, since those are always loaded on every page, but I might decide to change what's in BOOTSTRAP_MODULES. :) To be safe, I'd declare any dependency, even one that's in that collection at the time.
- Is there a recommended way for LiveElements to access each other on the server? For example, I click on an element of (client-side) Foo that invokes a method on (server-side) Foo; this then needs to update the state of (server-side) Bar, which needs to update (client-side) Bar. At the moment I'm using an manual ad-hoc registry, with the occasional (naughty?) obj.fragmentParent thrown in. I was wondering if there was a more robust method already in place that I had totally missed (like the LiveElements being assigned names upon creation, which can then be automagically found through something like page.findElementByName ( "Bar" )).
Unfortunately, names would have to be unique within the context of a page, which leads to the possibility of collisions. Resolving this would make it impossible to know the actual name of the element you want. :) I'd suggest instantiating your LiveElements with direct references to whatever other objects they need. This is somewhat similar to a problem many people have when implementing protocols with Twisted. I don't know of a general solution, but arranging for the right objects to be referencable from the right places should solve any particular case that comes up. Jean-Paul
On Thursday 17 May 2007 19:54:40 Jean-Paul Calderone wrote: Again, Jean-Paul, thanks for your reply; you've been very helpful.
Note that since the dependency resolution system works upwards, this mapping only needs one entry for this example to work:
jsModules.mapping = {u'Cutlery': 'cutlery.js'}
Ah, that's good to have confirmed. I was seeing the request to retrieve the top-level package come in and suspected that something of the sort was afoot.
I... think? so... I'm not sure if you were trying to demonstrate something by putting the jsModules mapping into the same file as the rest of the Python (since you pointed it out, I suspect you were, but I'm not sure what).
Oh, sorry. I didn't mean to imply that the jsModules mapping goes into the same file as the rest of the Python, merely that all the JS mappings point to the same JS file (cutlery.js) rather than having multiple files, one for each submodule of the JS package. But in any case, your pointing out that I can just use the top-level package name renders that moot. Regards, Ricky
participants (2)
-
Jean-Paul Calderone
-
kgi