1
# -*- test-case-name: twisted.web.test.test_woven -*-
2
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3
# See LICENSE for details.
8
from __future__ import nested_scopes
12
from twisted.web.microdom import parseString, Element, Node
13
from twisted.web import domhelpers
23
from twisted.python import components, failure
24
from twisted.python import reflect
25
from twisted.python import log
26
from twisted.internet import defer
28
viewFactory = view.viewFactory
29
document = parseString("<xml />", caseInsensitive=0, preserveCase=0)
31
missingPattern = Element("div", caseInsensitive=0, preserveCase=0)
32
missingPattern.setAttribute("style", "border: dashed red 1px; margin: 4px")
35
DOMWidgets are views which can be composed into bigger views.
45
class Widget(view.View):
47
A Widget wraps an object, its model, for display. The model can be a
48
simple Python object (string, list, etc.) or it can be an instance
49
of L{model.Model}. (The former case is for interface purposes, so that
50
the rest of the code does not have to treat simple objects differently
51
from Model instances.)
53
If the model is-a Model, there are two possibilities:
55
- we are being called to enable an operation on the model
56
- we are really being called to enable an operation on an attribute
57
of the model, which we will call the submodel
59
@cvar tagName: The tag name of the element that this widget creates. If this
60
is None, then the original Node will be cloned.
61
@cvar wantsAllNotifications: Indicate that this widget wants to recieve every
62
change notification from the main model, not just notifications that affect
64
@ivar model: If the current model is an L{model.Model}, then the result of
65
model.getData(). Otherwise the original object itself.
67
# wvupdate_xxx method signature: request, widget, data; returns None
69
# Don't do lots of work setting up my stacks; they will be passed to me
72
# Should we clear the node before we render the widget?
75
# If some code has to ask if a widget is livePage, the answer is yes
79
def __init__(self, model = None, submodel = None, setup = None, controller = None, viewStack=None, *args, **kwargs):
81
@type model: L{interfaces.IModel}
83
@param submodel: see L{Widget.setSubmodel}
84
@type submodel: String
88
self.errorFactory = Error
89
self.controller = controller
92
view.View.__init__(self, model)
94
self.templateNode = None
96
self.submodel = submodel
100
self.setupMethods = [setup]
102
self.setupMethods = []
103
self.viewStack = viewStack
104
self.initialize(*args, **kwargs)
111
def initialize(self, *args, **kwargs):
113
Use this method instead of __init__ to initialize your Widget, so you
114
don't have to deal with calling the __init__ of the superclass.
118
def setSubmodel(self, submodel):
120
I use the submodel to know which attribute in self.model I am responsible for
122
self.submodel = submodel
124
def getData(self, request=None):
126
I have a model; however since I am a widget I am only responsible
127
for a portion of that model. This method returns the portion I am
130
The return value of this may be a Deferred; if it is, then
131
L{setData} will be called once the result is available.
133
return self.model.getData(request)
135
def setData(self, request=None, data=None):
137
If the return value of L{getData} is a Deferred, I am called
138
when the result of the Deferred is available.
140
self.model.setData(request, data)
144
Add `item' to the children of the resultant DOM Node of this widget.
146
@type item: A DOM node or L{Widget}.
148
self._children.append(item)
150
def appendChild(self, item):
152
Add `item' to the children of the resultant DOM Node of this widget.
154
@type item: A DOM node or L{Widget}.
156
self._children.append(item)
158
def insert(self, index, item):
160
Insert `item' at `index' in the children list of the resultant DOM Node
163
@type item: A DOM node or L{Widget}.
165
self._children.insert(index, item)
167
def setNode(self, node):
169
Set a node for this widget to use instead of creating one programatically.
170
Useful for looking up a node in a template and using that.
172
# self.templateNode should always be the original, unmutated
173
# node that was in the HTML template.
174
if self.templateNode == None:
175
self.templateNode = node
178
def cleanNode(self, node):
180
Do your part, prevent infinite recursion!
183
if node.attributes.has_key('model'):
184
del node.attributes['model']
185
if node.attributes.has_key('view'):
186
del node.attributes['view']
187
if node.attributes.has_key('controller'):
188
del node.attributes['controller']
191
def generate(self, request, node):
192
data = self.getData(request)
193
if isinstance(data, defer.Deferred):
194
data.addCallback(self.setDataCallback, request, node)
195
data.addErrback(utils.renderFailure, request)
197
return self._regenerate(request, node, data)
199
def _regenerate(self, request, node, data):
201
self.setUp(request, node, data)
202
for setupMethod in self.setupMethods:
203
setupMethod(request, self, data)
204
# generateDOM should always get a reference to the
205
# templateNode from the original HTML
206
result = self.generateDOM(request, self.templateNode or node)
208
result.attributes['woven_class'] = reflect.qual(self.__class__)
211
def setDataCallback(self, result, request, node):
212
if isinstance(self.getData(request), defer.Deferred):
213
self.setData(request, result)
214
data = self.getData(request)
215
if isinstance(data, defer.Deferred):
217
warnings.warn("%r has returned a Deferred multiple times for the "
218
"same request; this is a potential infinite loop."
220
data.addCallback(self.setDataCallback, request, node)
221
data.addErrback(utils.renderFailure, request)
224
newNode = self._regenerate(request, node, result)
225
returnNode = self.dispatchResult(request, node, newNode)
226
# isinstance(Element) added because I was having problems with
227
# this code trying to call setAttribute on my RawTexts -radix 2003-5-28
228
if hasattr(self, 'outgoingId') and isinstance(returnNode, Element):
229
returnNode.attributes['id'] = self.outgoingId
230
self.handleNewNode(request, returnNode)
231
self.handleOutstanding(request)
233
self.getTopModel().subviews.update(self.subviews)
234
self.controller.domChanged(request, self, returnNode)
236
## We need to return the result along the callback chain
237
## so that any other views which added a setDataCallback
238
## to the same deferred will get the correct data.
241
def setUp(self, request, node, data):
243
Override this method to set up your Widget prior to generateDOM. This
244
is a good place to call methods like L{add}, L{insert}, L{__setitem__}
247
Overriding this method obsoletes overriding generateDOM directly, in
250
@type request: L{twisted.web.server.Request}.
251
@param node: The DOM node which this Widget is operating on.
252
@param data: The Model data this Widget is meant to operate upon.
256
def generateDOM(self, request, node):
258
@returns: A DOM Node to replace the Node in the template that this
259
Widget handles. This Node is created based on L{tagName},
260
L{children}, and L{attributes} (You should populate these
261
in L{setUp}, probably).
267
parent = node.parentNode
268
node.parentNode = None
269
old = node.cloneNode(1)
270
node.parentNode = parent
271
gen = become.generateDOM(request, node)
272
if old.attributes.has_key('model'):
273
del old.attributes['model']
274
del old.attributes['controller']
279
template = node.toxml()
282
self.tagName = self.templateNode.tagName
283
if node is not self.templateNode or self.tagName != self.templateNode.tagName:
284
parent = node.parentNode
285
node = document.createElement(self.tagName, caseInsensitive=0, preserveCase=0)
286
node.parentNode = parent
288
parentNode = node.parentNode
289
node.parentNode = None
291
new = node.cloneNode(0)
293
new = node.cloneNode(1)
294
node.parentNode = parentNode
295
node = self.cleanNode(new)
296
#print "NICE CLEAN NODE", node.toxml(), self._children
297
node.attributes.update(self.attributes)
298
for item in self._children:
299
if hasattr(item, 'generate'):
300
parentNode = node.parentNode
301
node.parentNode = None
302
item = item.generate(request, node.cloneNode(1))
303
node.parentNode = parentNode
304
node.appendChild(item)
305
#print "WE GOT A NODE", node.toxml()
309
def modelChanged(self, payload):
310
request = payload.get('request', None)
315
if payload.has_key(self.submodel):
316
data = payload[self.submodel]
318
data = self.getData(request)
319
newNode = self._regenerate(request, oldNode, data)
320
returnNode = self.dispatchResult(request, oldNode, newNode)
321
# shot in the dark: this seems to make *my* code work. probably will
322
# break if returnNode returns a Deferred, as it's supposed to be able
324
# self.viewStack.push(self)
325
# self.controller.controllerStack.push(self.controller)
326
self.handleNewNode(request, returnNode)
327
self.handleOutstanding(request)
328
self.controller.domChanged(request, self, returnNode)
330
def __setitem__(self, item, value):
332
Convenience syntax for adding attributes to the resultant DOM Node of
335
assert value is not None
336
self.attributes[item] = value
338
setAttribute = __setitem__
340
def __getitem__(self, item):
342
Convenience syntax for getting an attribute from the resultant DOM Node
345
return self.attributes[item]
347
getAttribute = __getitem__
349
def setError(self, request, message):
351
Convenience method for allowing a Controller to report an error to the
352
user. When this is called, a Widget of class self.errorFactory is instanciated
353
and set to self.become. When generate is subsequently called, self.become
354
will be responsible for mutating the DOM instead of this widget.
356
#print "setError called", self
357
id = self.attributes.get('id', '')
359
self.become = self.errorFactory(self.model, message)
360
self.become['id'] = id
361
# self.modelChanged({'request': request})
363
def getTopModel(self):
364
"""Get a reference to this page's top model object.
367
while top.parent is not None:
371
def getAllPatterns(self, name, default=missingPattern, clone=1, deep=1):
372
"""Get all nodes below this one which have a matching pattern attribute.
374
if self.slots.has_key(name):
375
slots = self.slots[name]
377
sm = self.submodel.split('/')[-1]
378
slots = domhelpers.locateNodes(self.templateNode, name + 'Of', sm)
380
# slots = domhelpers.locateNodes(self.templateNode, "pattern", name, noNesting=1)
381
matcher = lambda n, name=name: isinstance(n, Element) and \
382
n.attributes.has_key("pattern") and n.attributes["pattern"] == name
383
recurseMatcher = lambda n: isinstance(n, Element) and not n.attributes.has_key("view") and not n.attributes.has_key('model')
384
slots = domhelpers.findNodesShallowOnMatch(self.templateNode, matcher, recurseMatcher)
386
msg = 'WARNING: No template nodes were found '\
388
' or pattern="%s") for node %s (full submodel path %s)' % (name + "Of",
389
sm, name, self.templateNode, `self.submodel`)
390
if default is _RAISE:
394
if default is missingPattern:
395
newNode = missingPattern.cloneNode(1)
396
newNode.appendChild(document.createTextNode(msg))
401
self.slots[name] = slots
403
return [x.cloneNode(deep) for x in slots]
406
def getPattern(self, name, default=missingPattern, clone=1, deep=1):
407
"""Get a named slot from the incoming template node. Returns a copy
408
of the node and all its children. If there was more than one node with
409
the same slot identifier, they will be returned in a round-robin fashion.
411
slots = self.getAllPatterns(name, default=default, clone=0)
417
parentNode = slot.parentNode
418
slot.parentNode = None
419
clone = slot.cloneNode(deep)
420
if clone.attributes.has_key('pattern'):
421
del clone.attributes['pattern']
422
elif clone.attributes.has_key(name + 'Of'):
423
del clone.attributes[name + 'Of']
424
slot.parentNode = parentNode
426
clone.attributes['ofPattern'] = name + 'Of'
427
clone.attributes['nameOf'] = self.submodel.split('/')[-1]
430
slot.attributes['ofPattern'] = name + 'Of'
431
slot.attributes['nameOf'] = self.submodel.split('/')[-1]
434
def addUpdateMethod(self, updateMethod):
435
"""Add a method to this widget that will be called when the widget
436
is being rendered. The signature for this method should be
437
updateMethod(request, widget, data) where widget will be the
438
instance you are calling addUpdateMethod on.
440
self.setupMethods.append(updateMethod)
442
def addEventHandler(self, eventName, handler, *args):
443
"""Add an event handler to this widget. eventName is a string
444
indicating which javascript event handler should cause this
445
handler to fire. Handler is a callable that has the signature
446
handler(request, widget, *args).
448
def handlerUpdateStep(request, widget, data):
451
extraArgs += " ,'" + x.replace("'", "\\'") + "'"
452
widget[eventName] = "return woven_eventHandler('%s', this%s)" % (eventName, extraArgs)
453
setattr(self, 'wevent_' + eventName, handler)
454
self.addUpdateMethod(handlerUpdateStep)
456
def onEvent(self, request, eventName, *args):
457
"""Dispatch a client-side event to an event handler that was
458
registered using addEventHandler.
460
eventHandler = getattr(self, 'wevent_' + eventName, None)
461
if eventHandler is None:
462
raise NotImplementedError("A client side '%s' event occurred,"
463
" but there was no event handler registered on %s." %
466
eventHandler(request, self, *args)
469
class DefaultWidget(Widget):
470
def generate(self, request, node):
472
By default, we just return the node unchanged
478
parent = node.parentNode
479
node.parentNode = None
480
old = node.cloneNode(1)
481
node.parentNode = parent
482
gen = become.generateDOM(request, node)
483
del old.attributes['model']
484
gen.appendChild(self.cleanNode(old))
488
def modelChanged(self, payload):
489
"""We're not concerned if the model has changed.
494
class Attributes(Widget):
495
"""Set attributes on a node.
497
Assumes model is a dictionary of attributes.
500
def setUp(self, request, node, data):
501
for k, v in data.items():
507
A simple Widget that renders some text.
509
def __init__(self, model, raw=0, clear=1, *args, **kwargs):
511
@param model: The text to render.
512
@type model: A string or L{model.Model}.
513
@param raw: A boolean that specifies whether to render the text as
514
a L{domhelpers.RawText} or as a DOM TextNode.
517
self.clearNode = clear
518
Widget.__init__(self, model, *args, **kwargs)
520
def generate(self, request, node):
521
if self.templateNode is None:
523
return domhelpers.RawText(str(self.getData(request)))
525
return document.createTextNode(str(self.getData(request)))
526
return Widget.generate(self, request, node)
528
def setUp(self, request, node, data):
530
textNode = domhelpers.RawText(str(data))
532
textNode = document.createTextNode(str(data))
533
self.appendChild(textNode)
536
class ParagraphText(Widget):
538
Like a normal text widget, but it takes line breaks in the text and
539
formats them as HTML paragraphs.
541
def setUp(self, request, node, data):
542
nSplit = data.split('\n')
545
para = request.d.createElement('p', caseInsensitive=0, preserveCase=0)
546
para.appendChild(request.d.createTextNode(line))
551
A simple Widget that creates an `img' tag.
555
def setUp(self, request, node, data):
556
self['border'] = self.border
562
def __init__(self, model, message="", *args, **kwargs):
563
Widget.__init__(self, model, *args, **kwargs)
564
self.message = message
566
def generateDOM(self, request, node):
567
self['style'] = 'color: red'
568
self.add(Text(" " + self.message))
569
return Widget.generateDOM(self, request, node)
586
def setSubmodel(self, submodel):
587
self.submodel = submodel
588
self['name'] = submodel
590
def setUp(self, request, node, data):
591
if not self.attributes.has_key('name') and not node.attributes.get('name'):
595
id = self.attributes.get('id', node.attributes.get('id'))
599
if not self.attributes.has_key('value'):
600
self['value'] = str(data)
603
class CheckBox(Input):
604
def setUp(self, request, node, data):
605
self['type'] = 'checkbox'
606
Input.setUp(self, request, node, data)
609
class RadioButton(Input):
610
def setUp(self, request, node, data):
611
self['type'] = 'radio'
612
Input.setUp(self, request, node, data)
616
def setUp(self, request, node, data):
617
self['type'] = 'file'
618
Input.setUp(self, request, node, data)
622
def setUp(self, request, node, data):
623
self['type'] = 'hidden'
624
Input.setUp(self, request, node, data)
627
class InputText(Input):
628
def setUp(self, request, node, data):
629
self['type'] = 'text'
630
Input.setUp(self, request, node, data)
633
class PasswordText(Input):
635
I render a password input field.
637
def setUp(self, request, node, data):
638
self['type'] = 'password'
639
Input.setUp(self, request, node, data)
643
def setUp(self, request, node, data):
644
self['type'] = 'button'
645
Input.setUp(self, request, node, data)
652
class Option(Widget):
654
def initialize(self):
657
def setText(self, text):
659
Set the text to be displayed within the select menu.
663
def setValue(self, value):
664
self['value'] = str(value)
666
def setUp(self, request, node, data):
667
self.add(Text(self.text or data))
670
if not self.attributes.has_key('value'):
671
self['value'] = str(data)
673
class Anchor(Widget):
676
def initialize(self):
682
def setRaw(self, raw):
685
def setLink(self, href):
688
def setParameter(self, key, value):
689
self.parameters[key] = value
691
def setText(self, text):
694
def setUp(self, request, node, data):
696
params = urllib.urlencode(self.parameters)
698
href = href + '?' + params
699
self['href'] = href or str(data) + self.trailingSlash
702
self.add(Text(self.text or data, self.raw, 0))
705
class SubAnchor(Anchor):
706
def initialize(self):
708
"SubAnchor is deprecated, you might want either Anchor or DirectoryAnchor",
710
Anchor.initialize(self)
714
class DirectoryAnchor(Anchor):
718
def appendModel(newNode, modelName):
719
if newNode is None: return
720
curModel = newNode.attributes.get('model')
722
newModel = str(modelName)
724
newModel = '/'.join((curModel, str(modelName)))
725
newNode.attributes['model'] = newModel
730
I am a widget which knows how to generateDOM for a python list.
732
A List should be specified in the template HTML as so::
734
| <ul model="blah" view="List">
735
| <li pattern="emptyList">This will be displayed if the list
737
| <li pattern="listItem" view="Text">Foo</li>
740
If you have nested lists, you may also do something like this::
742
| <table model="blah" view="List">
743
| <tr pattern="listHeader"><th>A</th><th>B</th></tr>
744
| <tr pattern="emptyList"><td colspan='2'>***None***</td></tr>
745
| <tr pattern="listItem">
746
| <td><span view="Text" model="1" /></td>
747
| <td><span view="Text" model="2" /></td>
749
| <tr pattern="listFooter"><td colspan="2">All done!</td></tr>
752
Where blah is the name of a list on the model; eg::
754
| self.model.blah = ['foo', 'bar']
758
defaultItemView = "DefaultWidget"
759
def generateDOM(self, request, node):
760
node = Widget.generateDOM(self, request, node)
761
listHeaders = self.getAllPatterns('listHeader', None)
762
listFooters = self.getAllPatterns('listFooter', None)
763
emptyLists = self.getAllPatterns('emptyList', None)
764
domhelpers.clearNode(node)
766
node.childNodes.extend(listHeaders)
767
for n in listHeaders: n.parentNode = node
768
data = self.getData(request)
770
self._iterateData(node, self.submodel, data)
772
node.childNodes.extend(emptyLists)
773
for n in emptyLists: n.parentNode = node
775
node.childNodes.extend(listFooters)
776
for n in listFooters: n.parentNode = node
779
def _iterateData(self, parentNode, submodel, data):
781
retVal = [None] * len(data)
782
for itemNum in range(len(data)):
783
# theory: by appending copies of the li node
784
# each node will be handled once we exit from
785
# here because handleNode will then recurse into
786
# the newly appended nodes
788
newNode = self.getPattern('listItem')
789
if newNode.getAttribute('model') == '.':
790
newNode.removeAttribute('model')
791
elif not newNode.attributes.get("view"):
792
newNode.attributes["view"] = self.defaultItemView
793
appendModel(newNode, itemNum)
794
retVal[itemNum] = newNode
795
newNode.parentNode = parentNode
796
# parentNode.appendChild(newNode)
797
parentNode.childNodes.extend(retVal)
800
class KeyedList(List):
802
I am a widget which knows how to display the values stored within a
805
A KeyedList should be specified in the template HTML as so::
807
| <ul model="blah" view="KeyedList">
808
| <li pattern="emptyList">This will be displayed if the list is
810
| <li pattern="keyedListItem" view="Text">Foo</li>
813
I can take advantage of C{listHeader}, C{listFooter} and C{emptyList}
814
items just as a L{List} can.
816
def _iterateData(self, parentNode, submodel, data):
821
# Keys may be a tuple, if this is not a true dictionary but a dictionary-like object
822
if hasattr(keys, 'sort'):
825
newNode = self.getPattern('keyedListItem')
827
newNode = self.getPattern('item', _RAISE)
829
warnings.warn("itemOf= is deprecated, "
830
"please use listItemOf instead",
833
appendModel(newNode, key)
834
if not newNode.attributes.get("view"):
835
newNode.attributes["view"] = "DefaultWidget"
836
parentNode.appendChild(newNode)
839
class ColumnList(Widget):
840
def __init__(self, model, columns=1, start=0, end=0, *args, **kwargs):
841
Widget.__init__(self, model, *args, **kwargs)
842
self.columns = columns
846
def setColumns(self, columns):
847
self.columns = columns
849
def setStart(self, start):
852
def setEnd(self, end):
855
def setUp(self, request, node, data):
856
pattern = self.getPattern('columnListRow', clone=0)
858
listSize = self.end - self.start
859
if listSize > len(data):
863
for itemNum in range(listSize):
864
if itemNum % self.columns == 0:
865
row = self.getPattern('columnListRow')
866
domhelpers.clearNode(row)
867
node.appendChild(row)
869
newNode = self.getPattern('columnListItem')
871
appendModel(newNode, itemNum + self.start)
872
if not newNode.attributes.get("view"):
873
newNode.attributes["view"] = "DefaultWidget"
874
row.appendChild(newNode)
875
node.removeChild(pattern)
895
class RawText(Widget):
896
def generateDOM(self, request, node):
897
self.node = domhelpers.RawText(self.getData(request))
900
from types import StringType
903
"""A utility class for generating <a href='foo'>bar</a> tags.
906
def setUp(self, request, node, data):
907
# TODO: we ought to support Deferreds here for both text and href!
908
if isinstance(data, StringType):
909
node.tagName = self.tagName
910
node.attributes["href"] = data
913
txt = data.getSubmodel(request, "text").getData(request)
914
if not isinstance(txt, Node):
915
txt = document.createTextNode(txt)
916
lnk = data.getSubmodel(request, "href").getData(request)
918
node.tagName = self.tagName
919
domhelpers.clearNode(node)
920
node.appendChild(txt)
922
class RootRelativeLink(Link):
924
Just like a regular Link, only it makes the href relative to the
925
appRoot (that is, request.getRootURL()).
927
def setUp(self, request, node, data):
928
# hack, hack: some juggling so I can type less and share more
930
st = isinstance(data, StringType)
932
data = request.getRootURL() + '/' + data
933
Link.setUp(self, request, node, data)
935
self['href'] = request.getRootURL() + '/' + self['href']
937
class ExpandMacro(Widget):
938
"""A Macro expansion widget modeled after the METAL expander
939
in ZPT/TAL/METAL. Usage:
941
In the Page that is being rendered, place the ExpandMacro widget
942
on the node you want replaced with the Macro, and provide nodes
943
tagged with fill-slot= attributes which will fill slots in the Macro::
945
def wvfactory_myMacro(self, request, node, model):
948
macroFile="MyMacro.html",
952
<span fill-slot="greeting">Hello</span>
953
<span fill-slot="greetee">World</span>
956
Then, in your Macro template file ("MyMacro.html" in the above
957
example) designate a node as the macro node, and nodes
958
inside that as the slot nodes::
961
<h3><span slot="greeting" />, <span slot="greetee" />!</h3>
964
def __init__(self, model, macroTemplate = "", macroFile="", macroFileDirectory="", macroName="", **kwargs):
965
self.macroTemplate = macroTemplate
966
self.macroFile=macroFile
967
self.macroFileDirectory=macroFileDirectory
968
self.macroName=macroName
969
Widget.__init__(self, model, **kwargs)
971
def generate(self, request, node):
972
if self.macroTemplate:
975
template = self.macroTemplate).lookupTemplate(request)
979
templateFile=self.macroFile,
980
templateDirectory=self.macroFileDirectory).lookupTemplate(request)
982
## We are going to return the macro node from the metatemplate,
983
## after replacing any slot= nodes in it with fill-slot= nodes from `node'
984
macrolist = domhelpers.locateNodes(templ.childNodes, "macro", self.macroName)
985
assert len(macrolist) == 1, ("No macro or more than "
986
"one macro named %s found." % self.macroName)
989
del macro.attributes['macro']
990
slots = domhelpers.findElementsWithAttributeShallow(macro, "slot")
992
slotName = slot.attributes.get("slot")
993
fillerlist = domhelpers.locateNodes(node.childNodes, "fill-slot", slotName)
994
assert len(fillerlist) <= 1, "More than one fill-slot found with name %s" % slotName
996
filler = fillerlist[0]
997
filler.tagName = filler.endTagName = slot.tagName
998
del filler.attributes['fill-slot']
999
del slot.attributes['slot']
1000
filler.attributes.update(slot.attributes)
1001
slot.parentNode.replaceChild(filler, slot)
1005
class DeferredWidget(Widget):
1006
def setDataCallback(self, result, request, node):
1009
if isinstance(model, components.Componentized):
1010
view = model.getAdapter(interfaces.IView)
1011
if not view and hasattr(model, '__class__'):
1012
view = interfaces.IView(model, None)
1015
view["id"] = self.attributes.get('id', '')
1016
view.templateNode = node
1017
view.controller = self.controller
1018
return view.setDataCallback(result, request, node)
1020
return Widget.setDataCallback(self, result, request, node)
1023
class Break(Widget):
1024
"""Break into pdb when this widget is rendered. Mildly
1025
useful for debugging template structure, model stacks,
1028
def setUp(self, request, node, data):
1029
import pdb; pdb.set_trace()
1032
view.registerViewForModel(Text, model.StringModel)
1033
view.registerViewForModel(List, model.ListModel)
1034
view.registerViewForModel(KeyedList, model.DictionaryModel)
1035
view.registerViewForModel(Link, model.Link)
1036
view.registerViewForModel(DeferredWidget, model.DeferredWrapper)