~divmod-dev/divmod.org/trunk

« back to all changes in this revision

Viewing changes to Nevow/nevow/livepage.py

  • Committer: Jean-Paul Calderone
  • Date: 2014-06-29 20:33:04 UTC
  • mfrom: (2749.1.1 remove-epsilon-1325289)
  • Revision ID: exarkun@twistedmatrix.com-20140629203304-gdkmbwl1suei4m97
mergeĀ lp:~exarkun/divmod.org/remove-epsilon-1325289

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- test-case-name: nevow.test.test_livepage -*-
2
 
# Copyright (c) 2004 Divmod.
3
 
# See LICENSE for details.
4
 
 
5
 
"""
6
 
Previous generation Nevow Comet support.  Do not use this module.
7
 
 
8
 
@see: L{nevow.athena}
9
 
"""
10
 
 
11
 
import itertools, types
12
 
import warnings
13
 
 
14
 
from zope.interface import implements, Interface
15
 
 
16
 
from twisted.internet import defer, error
17
 
from twisted.internet.task import LoopingCall
18
 
from twisted.python import log
19
 
 
20
 
from nevow import tags, inevow, context, static, flat, rend, url, util, stan
21
 
 
22
 
# If you need to debug livepage itself or your livepage app, set this to true
23
 
DEBUG = False
24
 
 
25
 
_jslog = None
26
 
 
27
 
def _openjslog():
28
 
    global _jslog
29
 
    if _jslog is None:
30
 
        _jslog = file("js.log", "w")
31
 
        _jslog.write("**********\n")
32
 
        _jslog.flush()
33
 
    return _jslog
34
 
 
35
 
def jslog(*x):
36
 
    if DEBUG:
37
 
        mylog = _openjslog()
38
 
        for y in x:
39
 
            mylog.write(str(y))
40
 
        mylog.flush()
41
 
 
42
 
class JavascriptContext(context.WovenContext):
43
 
    def __init__(self, parent=None, tag=None, isAttrib=None,
44
 
                inJSSingleQuoteString=None, remembrances=None):
45
 
        super(JavascriptContext, self).__init__(
46
 
            parent, tag, inJS=True, isAttrib=isAttrib,
47
 
            inJSSingleQuoteString=inJSSingleQuoteString,
48
 
            remembrances=None)
49
 
 
50
 
 
51
 
class TimeoutException(Exception):
52
 
    pass
53
 
 
54
 
 
55
 
class ClientSideException(Exception):
56
 
    pass
57
 
 
58
 
 
59
 
class SingleQuote(object):
60
 
    def __init__(self, children):
61
 
        self.children = children
62
 
 
63
 
    def __repr__(self):
64
 
        return "%s(%s)" % (type(self).__name__, self.children)
65
 
 
66
 
 
67
 
def flattenSingleQuote(singleQuote, ctx):
68
 
    new = JavascriptContext(ctx, tags.invisible[singleQuote], inJSSingleQuoteString=True)
69
 
    return flat.serialize(singleQuote.children, new)
70
 
flat.registerFlattener(flattenSingleQuote, SingleQuote)
71
 
 
72
 
 
73
 
class _js(object):
74
 
    """
75
 
    Stan for Javascript. There is a convenience instance of this
76
 
    class named "js" in the livepage module which you should use
77
 
    instead of the _js class directly.
78
 
 
79
 
    Marker indicating literal Javascript should be rendered.
80
 
    No escaping will be performed.
81
 
 
82
 
    When inside a JavascriptContext, Nevow will automatically put
83
 
    apostrophe quote marks around any Python strings it renders.
84
 
    This makes turning a Python string into a JavaScript string very
85
 
    easy. However, there are often situations where you wish to
86
 
    generate some literal Javascript code and do not wish quote
87
 
    marks to be placed around it. In this situation, the js object
88
 
    should be used.
89
 
 
90
 
    The simplest usage is to simply pass a python string to js.
91
 
    When the js object is rendered, the python string will be
92
 
    rendered as if it were literal javascript. For example::
93
 
 
94
 
        client.send(js(\"alert('hello')\"))
95
 
 
96
 
    However, to make the generation of Javascript more
97
 
    convenient, the js object also provides safe implementations
98
 
    of __getattr__, __call__, and __getitem__. See the following
99
 
    examples to get an idea of how to use it. The Python code
100
 
    is to the left of the -> and the Javascript which results is
101
 
    to the right::
102
 
 
103
 
        js(\"alert('any javascript you like')\") -> alert('any javascript you like')
104
 
 
105
 
        js.window.title -> window.title
106
 
 
107
 
        js.document.getElementById('foo') -> document.getElementById('foo')
108
 
 
109
 
        js.myFunction('my argument') -> myFunction('my argument')
110
 
 
111
 
        js.myFunction(True, 5, \"it's a beautiful day\") -> myFunction(true, 5, 'it\\'s a beautiful day')
112
 
 
113
 
        js.document.all[\"something\"] -> document.all['something']
114
 
 
115
 
        js[1, 2] -> [1, 2]
116
 
 
117
 
    XXX TODO support javascript object literals somehow? (They look like dicts)
118
 
    perhaps like this::
119
 
 
120
 
        js[\"one\": 1, \"two\": 2] -> {\"one\": 1, \"two\": 2}
121
 
 
122
 
    The livepage module includes many convenient instances of the js object.
123
 
    It includes the literals::
124
 
 
125
 
        document
126
 
        window
127
 
        this
128
 
        self
129
 
 
130
 
    It includes shorthand for commonly called javascript functions::
131
 
 
132
 
        alert -> alert
133
 
        get -> document.getElementById
134
 
        set -> nevow_setNode
135
 
        append -> nevow_appendNode
136
 
        prepend -> nevow.prependNode
137
 
        insert -> nevow.insertNode
138
 
 
139
 
    It includes convenience calls against the client-side server object::
140
 
 
141
 
        server.handle('callMe') -> server.handle('callMe')
142
 
 
143
 
    It includes commonly-used fragments of javascript::
144
 
 
145
 
        stop -> ; return false;
146
 
        eol -> \\n
147
 
 
148
 
    Stop is used to prevent the browser from executing it's default
149
 
    event handler. For example::
150
 
 
151
 
        button(onclick=[server.handle('click'), stop]) -> <button onclick=\"server.handle('click'); return false;\" />
152
 
 
153
 
    EOL is currently required to separate statements (this requirement
154
 
    may go away in the future). For example::
155
 
 
156
 
        client.send([
157
 
            alert('hello'), eol,
158
 
            alert('goodbye')])
159
 
 
160
 
    XXX TODO: investigate whether rendering a \\n between list elements
161
 
    in a JavascriptContext has any ill effects.
162
 
    """
163
 
 
164
 
    def __init__(self, name=None):
165
 
        if name is None:
166
 
            name = []
167
 
        if isinstance(name, str):
168
 
            name = [stan.raw(name)]
169
 
        self._children = name
170
 
 
171
 
    def __getattr__(self, name):
172
 
        if name == 'clone':
173
 
            raise RuntimeError("Can't clone")
174
 
        if self._children:
175
 
            newchildren = self._children[:]
176
 
            newchildren.append(stan.raw('.'+name))
177
 
            return self.__class__(newchildren)
178
 
        return self.__class__(name)
179
 
 
180
 
    def __call__(self, *args):
181
 
        if not self._children:
182
 
            return self.__class__(args[0])
183
 
        newchildren = self._children[:]
184
 
        stuff = []
185
 
        for x in args:
186
 
            if isinstance(x, (
187
 
                basestring, stan.Tag, types.FunctionType,
188
 
                types.MethodType, types.UnboundMethodType)):
189
 
                x = stan.raw("'"), SingleQuote(x), stan.raw("'")
190
 
            stuff.append((x, stan.raw(',')))
191
 
        if stuff:
192
 
            stuff[-1] = stuff[-1][0]
193
 
        newchildren.extend([stan.raw('('), stuff, stan.raw(')')])
194
 
        return self.__class__(newchildren)
195
 
 
196
 
    def __getitem__(self, args):
197
 
        if not isinstance(args, (tuple, list)):
198
 
            args = (args,)
199
 
        newchildren = self._children[:]
200
 
        stuff = [(x, stan.raw(',')) for x in args]
201
 
        if stuff:
202
 
            stuff[-1] = stuff[-1][0]
203
 
        newchildren.extend([stan.raw("["), stuff, stan.raw("]")])
204
 
        return self.__class__(newchildren)
205
 
 
206
 
    def __iter__(self):
207
 
        """Prevent an infinite loop if someone tries to do
208
 
        for x in jsinstance:
209
 
        """
210
 
        raise NotImplementedError, "js instances are not iterable. (%r)" % (self, )
211
 
 
212
 
    def __repr__(self):
213
 
        return "%s(%r)" % (type(self).__name__, self._children)
214
 
def flattenJS(theJS, ctx):
215
 
    new = JavascriptContext(ctx, tags.invisible[theJS])
216
 
    return flat.serialize(theJS._children, new)
217
 
flat.registerFlattener(flattenJS, _js)
218
 
 
219
 
 
220
 
js = _js()
221
 
document = _js('document')
222
 
get = document.getElementById
223
 
window = _js('window')
224
 
this = _js('this')
225
 
self = _js('self')
226
 
server = _js('server')
227
 
alert = _js('alert')
228
 
stop = _js('; return false;')
229
 
eol = tags.raw('\n')
230
 
 
231
 
set = js.nevow_setNode
232
 
append = js.nevow_appendNode
233
 
prepend = js.nevow_prependNode
234
 
insert = js.nevow_insertNode
235
 
 
236
 
 
237
 
def assign(where, what):
238
 
    """Assign what to where. Equivalent to
239
 
    where = what;
240
 
    """
241
 
    return _js([where, stan.raw(" = "), what])
242
 
 
243
 
 
244
 
setq = assign # hee
245
 
 
246
 
 
247
 
def var(where, what):
248
 
    """Define local variable 'where' and assign 'what' to it.
249
 
    Equivalent to var where = what;
250
 
    """
251
 
    return _js([stan.raw("var "), where, stan.raw(" = "), what, stan.raw(";")])
252
 
 
253
 
 
254
 
def anonymous(block):
255
 
    """
256
 
    Turn block (any stan) into an anonymous JavaScript function
257
 
    which takes no arguments. Equivalent to::
258
 
 
259
 
        function () {
260
 
            block
261
 
        }
262
 
    """
263
 
    return _js([stan.raw("function() {\n"), block, stan.raw("\n}")])
264
 
 
265
 
 
266
 
class IClientHandle(Interface):
267
 
    def hookupOutput(output, finisher=None):
268
 
        """hook up an output conduit to this live evil instance.
269
 
        """
270
 
 
271
 
    def send(script):
272
 
        """send a script through the output conduit to the browser.
273
 
        If no output conduit is yet hooked up, buffer the script
274
 
        until one is.
275
 
        """
276
 
 
277
 
    def handleInput(identifier, *args):
278
 
        """route some input from the browser to the appropriate
279
 
        destination.
280
 
        """
281
 
 
282
 
 
283
 
class IHandlerFactory(Interface):
284
 
    def locateHandler(ctx, name):
285
 
        """Locate a handler callable with the given name.
286
 
        """
287
 
 
288
 
 
289
 
class _transient(object):
290
 
    def __init__(self, transientId, arguments=None):
291
 
        self.transientId = transientId
292
 
        if arguments is None:
293
 
            arguments = []
294
 
        elif isinstance(arguments, tuple):
295
 
            arguments = list(arguments)
296
 
        else:
297
 
            raise TypeError, "Arguments must be None or tuple"
298
 
        self.arguments = arguments
299
 
 
300
 
    def __call__(self, *arguments):
301
 
        return type(self)(self.transientId, arguments)
302
 
 
303
 
 
304
 
def flattenTransient(transient, ctx):
305
 
    thing = js.server.handle("--transient.%s" % (transient.transientId, ), *transient.arguments)
306
 
    return flat.serialize(thing, ctx)
307
 
flat.registerFlattener(flattenTransient, _transient)
308
 
 
309
 
 
310
 
class ClientHandle(object):
311
 
    """An object which represents the client-side webbrowser.
312
 
    """
313
 
    implements(IClientHandle)
314
 
 
315
 
    outputConduit = None
316
 
 
317
 
    def __init__(self, livePage, handleId, refreshInterval, targetTimeoutCount):
318
 
        self.refreshInterval = refreshInterval
319
 
        self.targetTimeoutCount = targetTimeoutCount
320
 
        self.timeoutCount = 0
321
 
        self.livePage = livePage
322
 
        self.handleId = handleId
323
 
        self.outputBuffer = []
324
 
        self.bufferDeferreds = []
325
 
        self.closed = False
326
 
        self.closeNotifications = []
327
 
        self.firstTime = True
328
 
        self.timeoutLoop = LoopingCall(self.checkTimeout)
329
 
        if refreshInterval:
330
 
            self.timeoutLoop.start(self.refreshInterval)
331
 
        self._transients = {}
332
 
        self.transientCounter = itertools.count().next
333
 
        self.nextId = itertools.count().next ## For backwards compatibility with handler
334
 
 
335
 
    def transient(self, what, *args):
336
 
        """Register a transient event handler, 'what'.
337
 
        The callable 'what' can only be invoked by the
338
 
        client once before being garbage collected.
339
 
        Additional attempts to invoke the handler
340
 
        will fail.
341
 
        """
342
 
        transientId = str(self.transientCounter())
343
 
        self._transients[transientId] = what
344
 
        return _transient(transientId, args)
345
 
 
346
 
    def popTransient(self, transientId):
347
 
        """Remove a transient previously registered
348
 
        by a call to transient. Normally, this will be done
349
 
        automatically when the transient is invoked.
350
 
        However, you can invoke it yourself if you wish
351
 
        to revoke the client's capability to call the
352
 
        transient handler.
353
 
        """
354
 
        if DEBUG: print "TRANSIENTS", self._transients
355
 
        return self._transients.pop(transientId)
356
 
 
357
 
    def _actuallySend(self, scripts):
358
 
        output = self.outputConduit
359
 
        written = []
360
 
        def writer(write):
361
 
            #print "WRITER", write
362
 
            written.append(write)
363
 
        def finisher(finish):
364
 
            towrite = '\n'.join(written)
365
 
            jslog("<<<<<<\n%s\n" % towrite)
366
 
            output.callback(towrite)
367
 
        flat.flattenFactory(scripts, self.outputContext, writer, finisher)
368
 
        self.outputConduit = None
369
 
        self.outputContext = None
370
 
 
371
 
    def send(self, *script):
372
 
        """Send the stan "script", which can be flattened to javascript,
373
 
        to the browser which is connected to this handle, and evaluate
374
 
        it in the context of the browser window.
375
 
        """
376
 
        if self.outputConduit:
377
 
            self._actuallySend(script)
378
 
        else:
379
 
            self.outputBuffer.append(script)
380
 
            self.outputBuffer.append(eol)
381
 
 
382
 
    def setOutput(self, ctx, output):
383
 
        self.timeoutCount = 0
384
 
        self.outputContext = ctx
385
 
        self.outputConduit = output
386
 
        if self.outputBuffer:
387
 
            if DEBUG: print "SENDING BUFFERED", self.outputBuffer
388
 
            self._actuallySend(self.outputBuffer)
389
 
            self.outputBuffer = []
390
 
 
391
 
    def _actuallyPassed(self, result, deferreds):
392
 
        for d in deferreds:
393
 
            d.callback(result)
394
 
 
395
 
    def _actuallyFailed(self, failure, deferreds):
396
 
        for d in deferreds:
397
 
            d.errback(failure)
398
 
 
399
 
    def checkTimeout(self):
400
 
        if self.outputConduit is not None:
401
 
            ## The browser is waiting for us, send a noop.
402
 
            self.send(_js('null;'))
403
 
            return
404
 
        self.timeoutCount += 1
405
 
        if self.timeoutCount >= self.targetTimeoutCount:
406
 
            ## This connection timed out.
407
 
            self._closeComplete(
408
 
                TimeoutException(
409
 
                    "This connection did not ACK in at least %s seconds." % (
410
 
                        self.targetTimeoutCount * self.refreshInterval, )))
411
 
 
412
 
    def outputGone(self, failure, output):
413
 
#        assert output == self.outputConduit
414
 
        # Twisted errbacks with a ConnectionDone when the client closes the
415
 
        # connection cleanly. Pretend it didn't happen and carry on.
416
 
        self.outputConduit = None
417
 
        if failure.check(error.ConnectionDone):
418
 
            self._closeComplete()
419
 
        else:
420
 
            self._closeComplete(failure)
421
 
        return None
422
 
 
423
 
    def _closeComplete(self, failure=None):
424
 
        if self.closed:
425
 
            return
426
 
        self.closed = True
427
 
        self.timeoutLoop.stop()
428
 
        self.timeoutLoop = None
429
 
        for notify in self.closeNotifications[:]:
430
 
            if failure is not None:
431
 
                notify.errback(failure)
432
 
            else:
433
 
                notify.callback(None)
434
 
        self.closeNotifications = []
435
 
 
436
 
    def notifyOnClose(self):
437
 
        """This will return a Deferred that will be fired when the
438
 
        connection is closed 'normally', i.e. in response to handle.close()
439
 
        . If the connection is lost in any other way (because the browser
440
 
        navigated to another page, the browser was shut down, the network
441
 
        connection was lost, or the timeout was reached), this will errback
442
 
        instead."""
443
 
        d = defer.Deferred()
444
 
        self.closeNotifications.append(d)
445
 
        return d
446
 
 
447
 
    def close(self, executeScriptBeforeClose=""):
448
 
        if DEBUG: print "CLOSE WAS CALLED"
449
 
        d = self.notifyOnClose()
450
 
        self.send(js.nevow_closeLive(executeScriptBeforeClose))
451
 
        return d
452
 
 
453
 
    def set(self, where, what):
454
 
        self.send(js.nevow_setNode(where, what))
455
 
 
456
 
    def prepend(self, where, what):
457
 
        self.send(js.nevow_prependNode(where, what))
458
 
 
459
 
    def append(self, where, what):
460
 
        self.send(js.nevow_appendNode(where, what))
461
 
 
462
 
    def alert(self, what):
463
 
        self.send(js.alert(what))
464
 
 
465
 
    def call(self, what, *args):
466
 
        self.send(js(what)(*args))
467
 
 
468
 
    def sendScript(self, string):
469
 
        warnings.warn(
470
 
            "[0.5] nevow.livepage.ClientHandle.sendScript is deprecated, use send instead.",
471
 
            DeprecationWarning,
472
 
            2)
473
 
        self.send(string)
474
 
 
475
 
 
476
 
class DefaultClientHandleFactory(object):
477
 
    clientHandleClass = ClientHandle
478
 
 
479
 
    def __init__(self):
480
 
        self.clientHandles = {}
481
 
        self.handleCounter = itertools.count().next
482
 
 
483
 
    def newClientHandle(self, livePage, refreshInterval, targetTimeoutCount):
484
 
        handleid = str(self.handleCounter())
485
 
        handle = self.clientHandleClass(
486
 
            livePage, handleid, refreshInterval, targetTimeoutCount)
487
 
        self.clientHandles[handleid] = handle
488
 
        handle.notifyOnClose().addBoth(lambda ign: self.deleteHandle(handleid))
489
 
        return handle
490
 
 
491
 
    def deleteHandle(self, handleid):
492
 
        del self.clientHandles[handleid]
493
 
 
494
 
    def getHandleForId(self, handleId):
495
 
        """Override this to restore old handles on demand.
496
 
        """
497
 
        return self.clientHandles[handleId]
498
 
 
499
 
theDefaultClientHandleFactory = DefaultClientHandleFactory()
500
 
 
501
 
 
502
 
class OutputHandlerResource:
503
 
    implements(inevow.IResource)
504
 
 
505
 
    def __init__(self, clientHandle):
506
 
        self.clientHandle = clientHandle
507
 
 
508
 
    def locateChild(self, ctx, segments):
509
 
        raise NotImplementedError()
510
 
 
511
 
    def renderHTTP(self, ctx):
512
 
        request = inevow.IRequest(ctx)
513
 
        neverEverCache(request)
514
 
        activeChannel(request)
515
 
        ctx.remember(jsExceptionHandler, inevow.ICanHandleException)
516
 
        request.channel._savedTimeOut = None # XXX TODO
517
 
        d = defer.Deferred()
518
 
        request.notifyFinish().addErrback(self.clientHandle.outputGone, d)
519
 
        jsContext = JavascriptContext(ctx, tags.invisible())
520
 
        self.clientHandle.livePage.rememberStuff(jsContext)
521
 
        jsContext.remember(self.clientHandle, IClientHandle)
522
 
        if self.clientHandle.firstTime:
523
 
            self.clientHandle.livePage.goingLive(jsContext, self.clientHandle)
524
 
            self.clientHandle.firstTime = False
525
 
        self.clientHandle.setOutput(jsContext, d)
526
 
        return d
527
 
 
528
 
 
529
 
class InputHandlerResource:
530
 
    implements(inevow.IResource)
531
 
 
532
 
    def __init__(self, clientHandle):
533
 
        self.clientHandle = clientHandle
534
 
 
535
 
    def locateChild(self, ctx, segments):
536
 
        raise NotImplementedError()
537
 
 
538
 
    def renderHTTP(self, ctx):
539
 
        self.clientHandle.timeoutCount = 0
540
 
 
541
 
        request = inevow.IRequest(ctx)
542
 
        neverEverCache(request)
543
 
        activeChannel(request)
544
 
        ctx.remember(self.clientHandle, IClientHandle)
545
 
        ctx.remember(jsExceptionHandler, inevow.ICanHandleException)
546
 
        self.clientHandle.livePage.rememberStuff(ctx)
547
 
 
548
 
        handlerName = request.args['handler-name'][0]
549
 
        arguments = request.args.get('arguments', ())
550
 
        jslog(">>>>>>\n%s %s\n" % (handlerName, arguments))
551
 
        if handlerName.startswith('--transient.'):
552
 
            handler = self.clientHandle.popTransient(handlerName.split('.')[-1])
553
 
        else:
554
 
            handler = self.clientHandle.livePage.locateHandler(
555
 
                ctx, request.args['handler-path'],
556
 
                handlerName)
557
 
 
558
 
        jsContext = JavascriptContext(ctx, tags.invisible[handler])
559
 
        towrite = []
560
 
 
561
 
        def writer(r):
562
 
            jslog("WRITE ", r)
563
 
            towrite.append(r)
564
 
 
565
 
        def finisher(r):
566
 
            jslog("FINISHED", r)
567
 
            writestr = ''.join(towrite)
568
 
            jslog("<><><>\n%s\n" % (writestr, ))
569
 
            request.write(writestr)
570
 
            request.finish()
571
 
            return r
572
 
 
573
 
        result = handler(jsContext, *arguments)
574
 
        jslog("RESULT ", result)
575
 
 
576
 
        if result is None:
577
 
            return defer.succeed('')
578
 
        return self.clientHandle.livePage.flattenFactory(result, jsContext,
579
 
                                            writer, finisher)
580
 
 
581
 
 
582
 
 
583
 
class DefaultClientHandlesResource(object):
584
 
    implements(inevow.IResource)
585
 
 
586
 
    clientResources = {
587
 
        'input': InputHandlerResource,
588
 
        'output': OutputHandlerResource,
589
 
        }
590
 
 
591
 
    clientFactory = theDefaultClientHandleFactory
592
 
 
593
 
    def locateChild(self, ctx, segments):
594
 
        handleId = segments[0]
595
 
        handlerType = segments[1]
596
 
        client = self.clientFactory.clientHandles[handleId]
597
 
 
598
 
        return self.clientResources[handlerType](client), segments[2:]
599
 
 
600
 
theDefaultClientHandlesResource = DefaultClientHandlesResource()
601
 
 
602
 
class attempt(defer.Deferred):
603
 
    """
604
 
    Attempt to do 'stuff' in the browser. callback on the server
605
 
    if 'stuff' executes without raising an exception. errback on the
606
 
    server if 'stuff' raises a JavaScript exception in the client.
607
 
 
608
 
    Used like this::
609
 
 
610
 
        def printIt(what):
611
 
            print "Woo!", what
612
 
 
613
 
        C = IClientHandle(ctx)
614
 
        C.send(
615
 
            attempt(js("1+1")).addCallback(printIt))
616
 
 
617
 
        C.send(
618
 
            attempt(js("thisWillFail")).addErrback(printIt))
619
 
    """
620
 
    def __init__(self, stuff):
621
 
        self.stuff = stuff
622
 
        defer.Deferred.__init__(self)
623
 
 
624
 
 
625
 
def flattenAttemptDeferred(d, ctx):
626
 
    def attemptComplete(ctx, result, reason=None):
627
 
        if result == 'success':
628
 
            d.callback(None)
629
 
        else:
630
 
            d.errback(ClientSideException(reason))
631
 
    transient = IClientHandle(ctx).transient(attemptComplete)
632
 
    return flat.serialize([
633
 
_js("""try {
634
 
    """),
635
 
    d.stuff,
636
 
_js("""
637
 
    """),
638
 
    transient('success'),
639
 
_js("""
640
 
} catch (e) {
641
 
    """),
642
 
    transient('failure', js.e),
643
 
_js("}")
644
 
        ], ctx)
645
 
flat.registerFlattener(flattenAttemptDeferred, attempt)
646
 
 
647
 
 
648
 
class IOutputEvent(Interface):
649
 
    pass
650
 
 
651
 
class IInputEvent(Interface):
652
 
    pass
653
 
 
654
 
 
655
 
class ExceptionHandler(object):
656
 
    def renderHTTP_exception(self, ctx, failure):
657
 
        log.msg("Exception during input event:")
658
 
        log.err(failure)
659
 
        request = inevow.IRequest(ctx)
660
 
        request.write("throw new Error('Server side error: %s')" % (failure.getErrorMessage().replace("'", "\\'").replace("\n", "\\\n"), ))
661
 
        request.finish()
662
 
 
663
 
    def renderInlineException(self, ctx, reason):
664
 
        """TODO: I don't even think renderInlineException is ever called by anybody
665
 
        """
666
 
        pass
667
 
 
668
 
 
669
 
jsExceptionHandler = ExceptionHandler()
670
 
 
671
 
 
672
 
def neverEverCache(request):
673
 
    """ Set headers to indicate that the response to this request should never,
674
 
    ever be cached.
675
 
    """
676
 
    request.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate')
677
 
    request.setHeader('Pragma', 'no-cache')
678
 
 
679
 
def activeChannel(request):
680
 
    """Mark this connection as a 'live' channel by setting the Connection: close
681
 
    header and flushing all headers immediately.
682
 
    """
683
 
    request.setHeader("Connection", "close")
684
 
    request.write('')
685
 
 
686
 
class LivePage(rend.Page):
687
 
    """
688
 
    A Page which is Live provides asynchronous, bidirectional RPC between
689
 
    Python on the server and JavaScript in the client browser. A LivePage must
690
 
    include the "liveglue" JavaScript which includes a unique identifier which
691
 
    is assigned to every page render of a LivePage and the JavaScript required
692
 
    for the client to communicate asynchronously with the server.
693
 
 
694
 
    A LivePage grants the client browser the capability of calling server-side
695
 
    Python methods using a small amount of JavaScript code. There are two
696
 
    types of Python handler methods, persistent handlers and transient handlers.
697
 
 
698
 
      - To grant the client the capability to call a persistent handler over and over
699
 
        as many times as it wishes, subclass LivePage and provide handle_foo
700
 
        methods. The client can then call handle_foo by executing the following
701
 
        JavaScript::
702
 
 
703
 
          server.handle('foo')
704
 
 
705
 
        handle_foo will be invoked because the default implementation of
706
 
        locateHandler looks for a method prefixed handle_*. To change this,
707
 
        override locateHandler to do what you wish.
708
 
 
709
 
      - To grant the client the capability of calling a handler once and
710
 
        exactly once, use ClientHandle.transient to register a callable and
711
 
        embed the return result in a page to render JavaScript which will
712
 
        invoke the transient handler when executed. For example::
713
 
 
714
 
            def render_clickable(self, ctx, data):
715
 
                def hello(ctx):
716
 
                    return livepage.alert(\"Hello, world. You can only click me once.\")
717
 
 
718
 
                return ctx.tag(onclick=IClientHandle(ctx).transient(hello))
719
 
 
720
 
        The return result of transient can also be called to pass additional
721
 
        arguments to the transient handler. For example::
722
 
 
723
 
            def render_choice(self, ctx, data):
724
 
                def chosen(ctx, choseWhat):
725
 
                    return livepage.set(
726
 
                        \"choosable\",
727
 
                        [\"Thanks for choosing \", choseWhat])
728
 
 
729
 
                chooser = IClientHandle(ctx).transient(chosen)
730
 
 
731
 
                return span(id=\"choosable\")[
732
 
                    \"Choose one:\",
733
 
                    p(onclick=chooser(\"one\"))[\"One\"],
734
 
                    p(onclick=chooser(\"two\"))[\"Two\"]]
735
 
 
736
 
        Note that the above situation displays temporary UI to the
737
 
        user. When the user invokes the chosen handler, the UI which
738
 
        allowed the user to invoke the chosen handler is removed from
739
 
        the client. Thus, it is important that the transient registration
740
 
        is deleted once it is invoked, otherwise uncollectable garbage
741
 
        would accumulate in the handler dictionary. It is also important
742
 
        that either the one or the two button consume the same handler,
743
 
        since it is an either/or choice. If two handlers were registered,
744
 
        the untaken choice would be uncollectable garbage.
745
 
    """
746
 
    refreshInterval = 30
747
 
    targetTimeoutCount = 3
748
 
 
749
 
    clientFactory = theDefaultClientHandleFactory
750
 
 
751
 
    def renderHTTP(self, ctx):
752
 
        if not self.cacheable:
753
 
            neverEverCache(inevow.IRequest(ctx))
754
 
        return rend.Page.renderHTTP(self, ctx)
755
 
 
756
 
    def locateHandler(self, ctx, path, name):
757
 
        ### XXX TODO: Handle path
758
 
        return getattr(self, 'handle_%s' % (name, ))
759
 
 
760
 
    def goingLive(self, ctx, handle):
761
 
        """This particular LivePage instance is 'going live' from the
762
 
        perspective of the ClientHandle 'handle'. Override this to
763
 
        get notified when a new browser window observes this page.
764
 
 
765
 
        This means that a new user is now looking at the page, an old
766
 
        user has refreshed the page, or an old user has opened a new
767
 
        window or tab onto the page.
768
 
 
769
 
        This is the first time the ClientHandle instance is available
770
 
        for general use by the server. This Page may wish to keep
771
 
        track of the ClientHandle instances depending on how your
772
 
        application is set up.
773
 
        """
774
 
        pass
775
 
 
776
 
    def child_livepage_client(self, ctx):
777
 
        return theDefaultClientHandlesResource
778
 
 
779
 
    # child_nevow_glue.js = static.File # see below
780
 
 
781
 
    def render_liveid(self, ctx, data):
782
 
        warnings.warn("You don't need a liveid renderer any more; just liveglue is fine.",
783
 
                      DeprecationWarning)
784
 
        return ''
785
 
 
786
 
    cacheable = False           # Set this to true to use ***HIGHLY***
787
 
                                # EXPERIMENTAL lazy ID allocation feature,
788
 
                                # which will allow your LivePage instances to
789
 
                                # be cached by clients.
790
 
 
791
 
    def render_liveglue(self, ctx, data):
792
 
        if not self.cacheable:
793
 
            handleId = "'", self.clientFactory.newClientHandle(
794
 
                self,
795
 
                self.refreshInterval,
796
 
                self.targetTimeoutCount).handleId, "'"
797
 
        else:
798
 
            handleId = 'null'
799
 
 
800
 
        return [
801
 
            tags.script(type="text/javascript")[
802
 
                "var nevow_clientHandleId = ", handleId ,";"],
803
 
            tags.script(type="text/javascript",
804
 
                        src=url.here.child('nevow_glue.js'))
805
 
            ]
806
 
 
807
 
 
808
 
setattr(
809
 
    LivePage,
810
 
    'child_nevow_glue.js',
811
 
    static.File(
812
 
        util.resource_filename('nevow', 'liveglue.js'),
813
 
        'text/javascript'))
814
 
 
815
 
 
816
 
glue = tags.directive('liveglue')
817
 
 
818
 
 
819
 
 
820
 
 
821
 
##### BACKWARDS COMPATIBILITY CODE
822
 
 
823
 
 
824
 
ctsTemplate = "nevow_clientToServerEvent('%s',this,''%s)%s"
825
 
handledEventPostlude = '; return false;'
826
 
 
827
 
 
828
 
class handler(object):
829
 
    callme = None
830
 
    args = ()
831
 
    identifier = None
832
 
    def __init__(self, *args, **kw):
833
 
        """**DEPRECATED** [0.5]
834
 
 
835
 
        Handler is now deprecated. To expose server-side code to the client
836
 
        to be called by JavaScript, read the LivePage docstring.
837
 
        """
838
 
        warnings.warn(
839
 
            "[0.5] livepage.handler is deprecated; Provide handle_foo methods (or override locateHandler) on your LivePage and use (in javascript) server.handle('foo'), or use ClientHandle.transient to register a one-shot handler capability.",
840
 
            DeprecationWarning,
841
 
            2)
842
 
        ## Handle working like a 2.4 decorator where calling handler returns a decorator
843
 
        if not callable(args[0]) or isinstance(args[0], _js):
844
 
            self.args = args
845
 
            return
846
 
        self.callme = args[0]
847
 
        self(*args[1:], **kw)
848
 
 
849
 
    def __call__(self, *args, **kw):
850
 
        if self.callme is None:
851
 
            self.callme = args[0]
852
 
            args = args[1:]
853
 
 
854
 
        self.args += args
855
 
        self.outsideAttribute = kw.get('outsideAttribute')
856
 
        bubble = kw.get('bubble')
857
 
        if bubble:
858
 
            self.postlude = ';'
859
 
        else:
860
 
            self.postlude = handledEventPostlude
861
 
 
862
 
        if 'identifier' in kw:
863
 
            self.identifier = kw['identifier']
864
 
 
865
 
        return self
866
 
 
867
 
    content = property(lambda self: flt(self))
868
 
 
869
 
 
870
 
def flattenHandler(handler, ctx):
871
 
    client = IClientHandle(ctx)
872
 
    iden = handler.identifier
873
 
    if iden is None:
874
 
        iden = client.nextId()
875
 
    iden = '--handler-%s' % (iden, )
876
 
    ## TODO this should be the IHandlerFactory instead of IResource
877
 
    setattr(IHandlerFactory(ctx), 'handle_%s' % (iden, ), handler.callme)
878
 
    isAttrib = not handler.outsideAttribute
879
 
    new = JavascriptContext(ctx, tags.invisible[handler.args], isAttrib=isAttrib)
880
 
 
881
 
    rv = flat.flatten(
882
 
        js.nevow_clientToServerEvent(*(iden, this, '') + handler.args),
883
 
        new)
884
 
    rv += handler.postlude
885
 
    return tags.xml(rv)
886
 
flat.registerFlattener(flattenHandler, handler)
887
 
 
888
 
 
889
 
def flt(stan, quote=True, client=None, handlerFactory=None):
890
 
    """Flatten some stan to a string suitable for embedding in a javascript
891
 
    string.
892
 
 
893
 
    If quote is True, apostrophe, quote, and newline will be quoted
894
 
    """
895
 
    warnings.warn("[0.5] livepage.flt is deprecated. Don't use it.", DeprecationWarning, 2)
896
 
    from nevow import testutil
897
 
    fr = testutil.FakeRequest()
898
 
    ctx = context.RequestContext(tag=fr)
899
 
    ctx.remember(client, IClientHandle)
900
 
    ctx.remember(handlerFactory, IHandlerFactory)
901
 
    ctx.remember(None, inevow.IData)
902
 
 
903
 
    fl = flat.flatten(stan, ctx=ctx)
904
 
    if quote:
905
 
        fl = fl.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n')
906
 
    return fl