[Twisted-web] Replacement for handler in LivePage?
Mike C. Fletcher
mcfletch at rogers.com
Thu Sep 15 14:48:25 MDT 2005
Hi all,
Have just done an "svn up" to the latest Nevow source and found that the
"handler" mechanism in LivePage is now deprecated, but there doesn't
seem to be a functionally equivalent construct provided. My use of
handlers was to create dynamically generated (and modifying) pages where
a complex nested structure (tree) was presented using a set of GUI
controllers which would each register themselves for servicing events to
the controls within their view (including the sub-views for the nested
items). Each controller is responsible for standard GUI-type
interactions, i.e. *multiple events*.
Now, obviously I could override the page's locateHandler and do all of
the registration and lookup manually, but it seems that this kind of
thing would be the *common* use case for any Ajax application. So, I'm
thinking we should really have an easy method for constructing such
callbacks.
This is something along the lines of what I'm thinking (untested
pseudo-code):
class Handler( object ):
"""New-style handler for Nevow, uses the same basic mechanism as
transient
We want to be able to produce dynamically-generated trees of
controls, which means that we need to be able to register multi-shot
event handlers live.
"""
bubble = True
def __init__( self, identifier, callable, *args, **named ):
"""Initialise the handler instance
identifier -- unique identifier assigned by the client handle
callable -- the target callable object
args -- arguments to the function (javascript arguments)
named -- carries non-javascript arguments, currently:
bubble -- if defined and False, prevent bubbling of the
generating event (i.e. "stop" after the handler)
"""
self.identifier = identifier
self.callable = callable
self.args = args
if named.has_key( 'bubble' ):
self.bubble = named['bubble']
def jsIdentifier( self ):
"""Retrieve the javascript callback identifier for this callback"""
return '**handler.%s'%(self.identifier)
def jsCall( self, ctx ):
"""Produce the javascript to call this Handler on the server"""
base = livepage.server.handle( self.jsIdentifier(), *self.args )
if not self.bubble:
result = base
else:
result = [
base,
stop
]
return livepage.flat.serialize( result, ctx )
def __call__( self, javascriptContext, *args ):
"""Do the final calling of the handler with the client-provided
values"""
try:
return self.callable( javascriptContext, *args )
except Exception, err:
log.error(
"""Failure during Javascript callback on %s(%s): %s""",
getattr(self.callable,'__name__',self.callable),
", ".join( [repr(a) for a in args] ),
log.getException( err ),
)
return None
def flattenHandler( handler, ctx ):
"""Redirect to flatten a handler instance"""
return handler.jsCall( ctx )
livepage.flat.registerFlattener(flattenHandler, Handler)
class ClientHandle( livepage.ClientHandle ):
"""ClientHandle providing for run-time registration of multi-use
callbacks"""
handlerCount = 0
def handler( self, callable, *args, **named ):
"""Create a new Handler object, assigning an ID automatically"""
self.handlerCount += 1
handle = Handler( self.handlerCount, callable, *args, **named )
if not hasattr( self, 'handlers' ):
self.handlers = {}
self.notifyOnClose().addBoth( self.cleanHandlers )
self.handlers[ handle.jsIdentifier() ] = handle
return handle
def cleanHandlers( self, result=None ):
"""Clean up the handler registry for this client handle"""
try:
del self.handlers
except AttributeError, err:
pass
return result
def getHandler( self, key ):
"""Retrieve named registered handler or None
key -- identifier for the handler
"""
try:
return self.handlers[ key ]
except KeyError, err:
return None
obviously the InputHandlerResource.renderHTTP method would need to be
taught to do the lookup for the handler registry as well. Still, the
point here is to make a callback-registration system that works as
transparently for dynamically generated content as the original handler
mechanism, i.e. it's just an automated system where you can generate
client.handler( callable, livepage.get('blah').value ) calls to register
callbacks for any piece of content you are producing.
Any memory-cleanup issues with the following scheme are present with the
current "transient" scheme, incidentally. If there's a really strong
need, one could even create a "register delete handler" on the Handler
objects so that you can tell the handler to de-register itself when a
given controller goes away, i.e.:
def deregisterOnDelete( self, targetObject, client ):
"""Deregister this handler from the client on deletion of
targetObject"""
return weakref.ref( targetObject, _Deregister( client ))
class _Deregister( object ):
def __init__( self, client ):
self.client = weakref.ref( client )
def __call__( self, targetWeak ):
client = self.client()
if client:
try:
del client.handlers[ self.jsIdentifier()]
except (AttributeError,KeyError), err:
pass
Of course, I could have missed something that takes care of this
use-case, I've only been playing with the new version for a few hours.
Have fun,
Mike
--
________________________________________________
Mike C. Fletcher
Designer, VR Plumber, Coder
http://www.vrplumber.com
http://blog.vrplumber.com
More information about the Twisted-web
mailing list