[Twisted-web] Replacement for handler in LivePage?
Mike C. Fletcher
mcfletch at rogers.com
Thu Sep 15 17:48:16 MDT 2005
Mike C. Fletcher wrote:
...
> 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):
And attached is a module that implements the functionality. It's
currently set up as a set of subclasses and functions that shadow those
in nevow.livepage.
Have fun,
Mike
--
________________________________________________
Mike C. Fletcher
Designer, VR Plumber, Coder
http://www.vrplumber.com
http://blog.vrplumber.com
-------------- next part --------------
"""Provide classic dynamic-code-friendly handler mechanism for Nevow"""
from nevow import rend, loaders, tags, flat, livepage, inevow
from twisted.internet import defer
import weakref
log = APPLICATION.getLog( 'livepage' )
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"""
client = livepage.IClientHandle(ctx)
if self.identifier is None:
self.identifier = client.nextId()
client.registerHandler( self )
base = livepage.server.handle( self.jsIdentifier(), *self.args )
if not self.bubble:
result = base
else:
result = [
base,
livepage.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:
log.debug( """Callback: %s, %r""", self.callable, args )
client = livepage.IClientHandle( javascriptContext )
return self.callable( client, *args )
except Exception, err:
log.error(
"""Failure during Javascript callback on %s %s: %s""",
getattr(self.callable,'__name__',self.callable),
args,
log.getException( err ),
)
return None
def deregisterOnDelete( self, targetObject, client ):
"""Deregister this handler from the client on deletion of targetObject"""
return weakref.ref( targetObject, _Deregister( client ))
class _Deregister( object ):
"""Class to deregister a registered handler when target dies"""
def __init__( self, client ):
self.client = weakref.ref( client )
def __call__( self, targetWeak ):
"""De-register the javascript handler now that target is gone"""
client = self.client()
if client:
try:
del client.handlers[ self.jsIdentifier()]
except (AttributeError,KeyError), err:
pass
def flattenHandler( handler, ctx ):
"""Redirect to flatten a handler instance"""
return handler.jsCall( ctx )
livepage.flat.registerFlattener(flattenHandler, Handler)
def handler( callable, *args, **named ):
"""Handler call for use without reference to the client object
See Handler for discussion of arguments
"""
return Handler( None, callable, *args, **named )
class InputHandlerResource( livepage.InputHandlerResource ):
"""Teach to support the **handler.ID names..."""
def renderHTTP(self, ctx):
"""Handle incoming HTTP-based XML-RPC call
This is far more verbose than it should be because the base function
doesn't provide resolveHandler( ctx ), so we have to duplicate the
rest of the function just to overload that piece of functionality.
"""
self.clientHandle.timeoutCount = 0
request = inevow.IRequest(ctx)
livepage.neverEverCache(request)
livepage.activeChannel(request)
ctx.remember(self.clientHandle, livepage.IClientHandle)
ctx.remember(livepage.jsExceptionHandler, inevow.ICanHandleException)
handlerName = request.args['handler-name'][0]
arguments = request.args.get('arguments', ())
livepage.jslog(">>>>>>\n%s %s\n" % (handlerName, arguments))
handler = self.resolveHandler( ctx )
jsContext = livepage.JavascriptContext(ctx, tags.invisible[handler])
towrite = []
def writer(r):
livepage.jslog("WRITE ", r)
towrite.append(r)
def finisher(r):
livepage.jslog("FINISHED", r)
writestr = ''.join(towrite)
livepage.jslog("<><><>\n%s\n" % (writestr, ))
request.write(writestr)
request.finish()
return r
result = handler(jsContext, *arguments)
livepage.jslog("RESULT ", result)
if result is None:
return defer.succeed('')
return self.clientHandle.livePage.flattenFactory(result, jsContext,
writer, finisher)
def resolveHandler( self, ctx ):
"""Resolve the handler for the given context (request)"""
request = inevow.IRequest(ctx)
handlerName = request.args['handler-name'][0]
handler = self.clientHandle.getHandler( handlerName )
if handler is None:
if handlerName.startswith('--transient.'):
handler = self.clientHandle.popTransient(
handlerName.split('.')[-1]
)
else:
handler = self.clientHandle.livePage.locateHandler(
ctx, request.args['handler-path'],
handlerName
)
return handler
class ClientHandle( livepage.ClientHandle ):
"""ClientHandle providing for run-time registration of multi-use callbacks"""
def handler( self, callable, *args, **named ):
"""Create a new Handler object, assigning an ID automatically
With the LivePage below, this allows for doing
IClientHandle( ctx ).handler( callable, arg, arg1, arg2 )
whereever you see a context instance. Of course, we also support
the original handler top-level function as well.
"""
id = self.nextId()
handle = Handler( id, callable, *args, **named )
return self.registerHandler( handler )
def registerHandler( self, handler ):
"""Register a handler (must already have identifier)
handler -- callable handler taking a client instance and having a
jsIdentifier() method.
Side-effect: creates the "handlers" attribute and registers for
deletion of that attribute on loss of connection.
returns handler
"""
if not hasattr( self, 'handlers' ):
self.handlers = {}
self.notifyOnClose().addBoth( self.cleanHandlers )
self.handlers[ handler.jsIdentifier() ] = handler
return handler
def cleanHandlers( self, result=None ):
"""Clean up the handler registry for this client handle
Called by a notifyOnClose deferred
"""
log.debug( """Cleaning up handlers for client handler %s""", self )
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, exactly as returned from
jsIdentifier() on the handler (no pre-processing is done).
"""
try:
return self.handlers[ key ]
except KeyError, err:
return None
class DefaultClientHandleFactory( livepage.DefaultClientHandleFactory ):
"""Override declaration of clientHandleClass"""
clientHandleClass = ClientHandle
theDefaultClientHandleFactory = DefaultClientHandleFactory()
class DefaultClientHandlesResource(livepage.DefaultClientHandlesResource):
"""Override declarations of Input and Output handlers and clientFactory"""
clientResources = {
'input': InputHandlerResource,
'output': livepage.OutputHandlerResource,
}
clientFactory = theDefaultClientHandleFactory
theDefaultClientHandlesResource = DefaultClientHandlesResource()
class LivePage( livepage.LivePage):
"""Base class for Cinemon LivePage instances
Initialise as LivePage( content=SomeFragment(), target=target )
LivePages are resources, so can be directly returned from
traversal (i.e. don't need extra wrapping.
"""
clientFactory = theDefaultClientHandleFactory
def renderHTTP(self, ctx):
"""Override to store the client-handle in the main page context"""
handle = self.clientFactory.newClientHandle(
self, self.refreshInterval,self.targetTimeoutCount
)
ctx.remember( handle, livepage.IClientHandle )
return super( LivePage, self ).renderHTTP( ctx )
def render_liveglue(self, ctx, data):
"""Override to use already-created client handle (from renderHTTP)"""
if not self.cacheable:
handle = livepage.IClientHandle( ctx )
handleId = "'", handle.handleId, "'"
else:
handleId = 'null'
return [
tags.script(type="text/javascript")[
"var nevow_clientHandleId = ", handleId ,";"],
tags.script(type="text/javascript",
src=livepage.url.here.child('nevow_glue.js'))
]
def child_livepage_client(self, ctx):
"""Override the default client-handles-resources lookup to use local"""
return theDefaultClientHandlesResource
More information about the Twisted-web
mailing list