[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