Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(9)

Side by Side Diff: third_party/twisted_8_1/twisted/web/woven/widgets.py

Issue 12261012: Remove third_party/twisted_8_1 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # -*- test-case-name: twisted.web.test.test_woven -*-
2 # Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5
6 # DOMWidgets
7
8 from __future__ import nested_scopes
9
10 import urllib
11 import warnings
12 from twisted.web.microdom import parseString, Element, Node
13 from twisted.web import domhelpers
14
15
16 #sibling imports
17 import model
18 import template
19 import view
20 import utils
21 import interfaces
22
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
27
28 viewFactory = view.viewFactory
29 document = parseString("<xml />", caseInsensitive=0, preserveCase=0)
30
31 missingPattern = Element("div", caseInsensitive=0, preserveCase=0)
32 missingPattern.setAttribute("style", "border: dashed red 1px; margin: 4px")
33
34 """
35 DOMWidgets are views which can be composed into bigger views.
36 """
37
38 DEBUG = 0
39
40 _RAISE = 1
41
42 class Dummy:
43 pass
44
45 class Widget(view.View):
46 """
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.)
52
53 If the model is-a Model, there are two possibilities:
54
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
58
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 ever y
62 change notification from the main model, not just notifications that a ffect
63 its model.
64 @ivar model: If the current model is an L{model.Model}, then the result of
65 model.getData(). Otherwise the original object itself.
66 """
67 # wvupdate_xxx method signature: request, widget, data; returns None
68
69 # Don't do lots of work setting up my stacks; they will be passed to me
70 setupStacks = 0
71
72 # Should we clear the node before we render the widget?
73 clearNode = 0
74
75 # If some code has to ask if a widget is livePage, the answer is yes
76 livePage = 1
77
78 tagName = None
79 def __init__(self, model = None, submodel = None, setup = None, controller = None, viewStack=None, *args, **kwargs):
80 """
81 @type model: L{interfaces.IModel}
82
83 @param submodel: see L{Widget.setSubmodel}
84 @type submodel: String
85
86 @type setup: Callable
87 """
88 self.errorFactory = Error
89 self.controller = controller
90 self.become = None
91 self._reset()
92 view.View.__init__(self, model)
93 self.node = None
94 self.templateNode = None
95 if submodel:
96 self.submodel = submodel
97 else:
98 self.submodel = ""
99 if setup:
100 self.setupMethods = [setup]
101 else:
102 self.setupMethods = []
103 self.viewStack = viewStack
104 self.initialize(*args, **kwargs)
105
106 def _reset(self):
107 self.attributes = {}
108 self.slots = {}
109 self._children = []
110
111 def initialize(self, *args, **kwargs):
112 """
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.
115 """
116 pass
117
118 def setSubmodel(self, submodel):
119 """
120 I use the submodel to know which attribute in self.model I am responsibl e for
121 """
122 self.submodel = submodel
123
124 def getData(self, request=None):
125 """
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
128 responsible for.
129
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.
132 """
133 return self.model.getData(request)
134
135 def setData(self, request=None, data=None):
136 """
137 If the return value of L{getData} is a Deferred, I am called
138 when the result of the Deferred is available.
139 """
140 self.model.setData(request, data)
141
142 def add(self, item):
143 """
144 Add `item' to the children of the resultant DOM Node of this widget.
145
146 @type item: A DOM node or L{Widget}.
147 """
148 self._children.append(item)
149
150 def appendChild(self, item):
151 """
152 Add `item' to the children of the resultant DOM Node of this widget.
153
154 @type item: A DOM node or L{Widget}.
155 """
156 self._children.append(item)
157
158 def insert(self, index, item):
159 """
160 Insert `item' at `index' in the children list of the resultant DOM Node
161 of this widget.
162
163 @type item: A DOM node or L{Widget}.
164 """
165 self._children.insert(index, item)
166
167 def setNode(self, node):
168 """
169 Set a node for this widget to use instead of creating one programaticall y.
170 Useful for looking up a node in a template and using that.
171 """
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
176 self.node = node
177
178 def cleanNode(self, node):
179 """
180 Do your part, prevent infinite recursion!
181 """
182 if not DEBUG:
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']
189 return node
190
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)
196 return data
197 return self._regenerate(request, node, data)
198
199 def _regenerate(self, request, node, data):
200 self._reset()
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)
207 if DEBUG:
208 result.attributes['woven_class'] = reflect.qual(self.__class__)
209 return result
210
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):
216 import warnings
217 warnings.warn("%r has returned a Deferred multiple times for the "
218 "same request; this is a potential infinite loop."
219 % self.getData)
220 data.addCallback(self.setDataCallback, request, node)
221 data.addErrback(utils.renderFailure, request)
222 return data
223
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)
232 if self.subviews:
233 self.getTopModel().subviews.update(self.subviews)
234 self.controller.domChanged(request, self, returnNode)
235
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.
239 return result
240
241 def setUp(self, request, node, data):
242 """
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__}
245 and L{__getitem__}.
246
247 Overriding this method obsoletes overriding generateDOM directly, in
248 most cases.
249
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.
253 """
254 pass
255
256 def generateDOM(self, request, node):
257 """
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).
262 """
263 if self.become:
264 #print "becoming"
265 become = self.become
266 self.become = None
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']
275 gen.appendChild(old)
276 self.node = gen
277 return gen
278 if DEBUG:
279 template = node.toxml()
280 log.msg(template)
281 if not self.tagName:
282 self.tagName = self.templateNode.tagName
283 if node is not self.templateNode or self.tagName != self.templateNode.ta gName:
284 parent = node.parentNode
285 node = document.createElement(self.tagName, caseInsensitive=0, prese rveCase=0)
286 node.parentNode = parent
287 else:
288 parentNode = node.parentNode
289 node.parentNode = None
290 if self.clearNode:
291 new = node.cloneNode(0)
292 else:
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()
306 self.node = node
307 return self.node
308
309 def modelChanged(self, payload):
310 request = payload.get('request', None)
311 if request is None:
312 request = Dummy()
313 request.d = document
314 oldNode = self.node
315 if payload.has_key(self.submodel):
316 data = payload[self.submodel]
317 else:
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
323 # to do -glyph
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)
329
330 def __setitem__(self, item, value):
331 """
332 Convenience syntax for adding attributes to the resultant DOM Node of
333 this widget.
334 """
335 assert value is not None
336 self.attributes[item] = value
337
338 setAttribute = __setitem__
339
340 def __getitem__(self, item):
341 """
342 Convenience syntax for getting an attribute from the resultant DOM Node
343 of this widget.
344 """
345 return self.attributes[item]
346
347 getAttribute = __getitem__
348
349 def setError(self, request, message):
350 """
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 instan ciated
353 and set to self.become. When generate is subsequently called, self.becom e
354 will be responsible for mutating the DOM instead of this widget.
355 """
356 #print "setError called", self
357 id = self.attributes.get('id', '')
358
359 self.become = self.errorFactory(self.model, message)
360 self.become['id'] = id
361 # self.modelChanged({'request': request})
362
363 def getTopModel(self):
364 """Get a reference to this page's top model object.
365 """
366 top = self.model
367 while top.parent is not None:
368 top = top.parent
369 return top
370
371 def getAllPatterns(self, name, default=missingPattern, clone=1, deep=1):
372 """Get all nodes below this one which have a matching pattern attribute.
373 """
374 if self.slots.has_key(name):
375 slots = self.slots[name]
376 else:
377 sm = self.submodel.split('/')[-1]
378 slots = domhelpers.locateNodes(self.templateNode, name + 'Of', sm)
379 if not slots:
380 # slots = domhelpers.locateNodes(self.templateNode, "pattern", na me, noNesting=1)
381 matcher = lambda n, name=name: isinstance(n, Element) and \
382 n.attributes.has_key("pattern") and n.attributes["pa ttern"] == name
383 recurseMatcher = lambda n: isinstance(n, Element) and not n.attr ibutes.has_key("view") and not n.attributes.has_key('model')
384 slots = domhelpers.findNodesShallowOnMatch(self.templateNode, ma tcher, recurseMatcher)
385 if not slots:
386 msg = 'WARNING: No template nodes were found '\
387 '(tagged %s="%s"'\
388 ' or pattern="%s") for node %s (full submodel path %s)' % (name + "Of",
389 sm, name, self.templateNode, `self.s ubmodel`)
390 if default is _RAISE:
391 raise Exception(msg)
392 if DEBUG:
393 warnings.warn(msg)
394 if default is missingPattern:
395 newNode = missingPattern.cloneNode(1)
396 newNode.appendChild(document.createTextNode(msg))
397 return [newNode]
398 if default is None:
399 return None
400 return [default]
401 self.slots[name] = slots
402 if clone:
403 return [x.cloneNode(deep) for x in slots]
404 return slots
405
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 .
410 """
411 slots = self.getAllPatterns(name, default=default, clone=0)
412 if slots is None:
413 return None
414 slot = slots.pop(0)
415 slots.append(slot)
416 if clone:
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
425 if DEBUG:
426 clone.attributes['ofPattern'] = name + 'Of'
427 clone.attributes['nameOf'] = self.submodel.split('/')[-1]
428 return clone
429 if DEBUG:
430 slot.attributes['ofPattern'] = name + 'Of'
431 slot.attributes['nameOf'] = self.submodel.split('/')[-1]
432 return slot
433
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.
439 """
440 self.setupMethods.append(updateMethod)
441
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).
447 """
448 def handlerUpdateStep(request, widget, data):
449 extraArgs = ''
450 for x in args:
451 extraArgs += " ,'" + x.replace("'", "\\'") + "'"
452 widget[eventName] = "return woven_eventHandler('%s', this%s)" % (eve ntName, extraArgs)
453 setattr(self, 'wevent_' + eventName, handler)
454 self.addUpdateMethod(handlerUpdateStep)
455
456 def onEvent(self, request, eventName, *args):
457 """Dispatch a client-side event to an event handler that was
458 registered using addEventHandler.
459 """
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." %
464 (eventName, self))
465
466 eventHandler(request, self, *args)
467
468
469 class DefaultWidget(Widget):
470 def generate(self, request, node):
471 """
472 By default, we just return the node unchanged
473 """
474 self.cleanNode(node)
475 if self.become:
476 become = self.become
477 self.become = None
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))
485 return gen
486 return node
487
488 def modelChanged(self, payload):
489 """We're not concerned if the model has changed.
490 """
491 pass
492
493
494 class Attributes(Widget):
495 """Set attributes on a node.
496
497 Assumes model is a dictionary of attributes.
498 """
499
500 def setUp(self, request, node, data):
501 for k, v in data.items():
502 self[k] = v
503
504
505 class Text(Widget):
506 """
507 A simple Widget that renders some text.
508 """
509 def __init__(self, model, raw=0, clear=1, *args, **kwargs):
510 """
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.
515 """
516 self.raw = raw
517 self.clearNode = clear
518 Widget.__init__(self, model, *args, **kwargs)
519
520 def generate(self, request, node):
521 if self.templateNode is None:
522 if self.raw:
523 return domhelpers.RawText(str(self.getData(request)))
524 else:
525 return document.createTextNode(str(self.getData(request)))
526 return Widget.generate(self, request, node)
527
528 def setUp(self, request, node, data):
529 if self.raw:
530 textNode = domhelpers.RawText(str(data))
531 else:
532 textNode = document.createTextNode(str(data))
533 self.appendChild(textNode)
534
535
536 class ParagraphText(Widget):
537 """
538 Like a normal text widget, but it takes line breaks in the text and
539 formats them as HTML paragraphs.
540 """
541 def setUp(self, request, node, data):
542 nSplit = data.split('\n')
543 for line in nSplit:
544 if line.strip():
545 para = request.d.createElement('p', caseInsensitive=0, preserveC ase=0)
546 para.appendChild(request.d.createTextNode(line))
547 self.add(para)
548
549 class Image(Widget):
550 """
551 A simple Widget that creates an `img' tag.
552 """
553 tagName = 'img'
554 border = '0'
555 def setUp(self, request, node, data):
556 self['border'] = self.border
557 self['src'] = data
558
559
560 class Error(Widget):
561 tagName = 'span'
562 def __init__(self, model, message="", *args, **kwargs):
563 Widget.__init__(self, model, *args, **kwargs)
564 self.message = message
565
566 def generateDOM(self, request, node):
567 self['style'] = 'color: red'
568 self.add(Text(" " + self.message))
569 return Widget.generateDOM(self, request, node)
570
571
572 class Div(Widget):
573 tagName = 'div'
574
575
576 class Span(Widget):
577 tagName = 'span'
578
579
580 class Br(Widget):
581 tagName = 'br'
582
583
584 class Input(Widget):
585 tagName = 'input'
586 def setSubmodel(self, submodel):
587 self.submodel = submodel
588 self['name'] = submodel
589
590 def setUp(self, request, node, data):
591 if not self.attributes.has_key('name') and not node.attributes.get('name '):
592 if self.submodel:
593 id = self.submodel
594 else:
595 id = self.attributes.get('id', node.attributes.get('id'))
596 self['name'] = id
597 if data is None:
598 data = ''
599 if not self.attributes.has_key('value'):
600 self['value'] = str(data)
601
602
603 class CheckBox(Input):
604 def setUp(self, request, node, data):
605 self['type'] = 'checkbox'
606 Input.setUp(self, request, node, data)
607
608
609 class RadioButton(Input):
610 def setUp(self, request, node, data):
611 self['type'] = 'radio'
612 Input.setUp(self, request, node, data)
613
614
615 class File(Input):
616 def setUp(self, request, node, data):
617 self['type'] = 'file'
618 Input.setUp(self, request, node, data)
619
620
621 class Hidden(Input):
622 def setUp(self, request, node, data):
623 self['type'] = 'hidden'
624 Input.setUp(self, request, node, data)
625
626
627 class InputText(Input):
628 def setUp(self, request, node, data):
629 self['type'] = 'text'
630 Input.setUp(self, request, node, data)
631
632
633 class PasswordText(Input):
634 """
635 I render a password input field.
636 """
637 def setUp(self, request, node, data):
638 self['type'] = 'password'
639 Input.setUp(self, request, node, data)
640
641
642 class Button(Input):
643 def setUp(self, request, node, data):
644 self['type'] = 'button'
645 Input.setUp(self, request, node, data)
646
647
648 class Select(Input):
649 tagName = 'select'
650
651
652 class Option(Widget):
653 tagName = 'option'
654 def initialize(self):
655 self.text = ''
656
657 def setText(self, text):
658 """
659 Set the text to be displayed within the select menu.
660 """
661 self.text = text
662
663 def setValue(self, value):
664 self['value'] = str(value)
665
666 def setUp(self, request, node, data):
667 self.add(Text(self.text or data))
668 if data is None:
669 data = ''
670 if not self.attributes.has_key('value'):
671 self['value'] = str(data)
672
673 class Anchor(Widget):
674 tagName = 'a'
675 trailingSlash = ''
676 def initialize(self):
677 self.baseHREF = ''
678 self.parameters = {}
679 self.raw = 0
680 self.text = ''
681
682 def setRaw(self, raw):
683 self.raw = raw
684
685 def setLink(self, href):
686 self.baseHREF= href
687
688 def setParameter(self, key, value):
689 self.parameters[key] = value
690
691 def setText(self, text):
692 self.text = text
693
694 def setUp(self, request, node, data):
695 href = self.baseHREF
696 params = urllib.urlencode(self.parameters)
697 if params:
698 href = href + '?' + params
699 self['href'] = href or str(data) + self.trailingSlash
700 if data is None:
701 data = ""
702 self.add(Text(self.text or data, self.raw, 0))
703
704
705 class SubAnchor(Anchor):
706 def initialize(self):
707 warnings.warn(
708 "SubAnchor is deprecated, you might want either Anchor or DirectoryA nchor",
709 DeprecationWarning)
710 Anchor.initialize(self)
711
712
713
714 class DirectoryAnchor(Anchor):
715 trailingSlash = '/'
716
717
718 def appendModel(newNode, modelName):
719 if newNode is None: return
720 curModel = newNode.attributes.get('model')
721 if curModel is None:
722 newModel = str(modelName)
723 else:
724 newModel = '/'.join((curModel, str(modelName)))
725 newNode.attributes['model'] = newModel
726
727
728 class List(Widget):
729 """
730 I am a widget which knows how to generateDOM for a python list.
731
732 A List should be specified in the template HTML as so::
733
734 | <ul model="blah" view="List">
735 | <li pattern="emptyList">This will be displayed if the list
736 | is empty.</li>
737 | <li pattern="listItem" view="Text">Foo</li>
738 | </ul>
739
740 If you have nested lists, you may also do something like this::
741
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>
748 | </tr>
749 | <tr pattern="listFooter"><td colspan="2">All done!</td></tr>
750 | </table>
751
752 Where blah is the name of a list on the model; eg::
753
754 | self.model.blah = ['foo', 'bar']
755
756 """
757 tagName = None
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)
765 if listHeaders:
766 node.childNodes.extend(listHeaders)
767 for n in listHeaders: n.parentNode = node
768 data = self.getData(request)
769 if data:
770 self._iterateData(node, self.submodel, data)
771 elif emptyLists:
772 node.childNodes.extend(emptyLists)
773 for n in emptyLists: n.parentNode = node
774 if listFooters:
775 node.childNodes.extend(listFooters)
776 for n in listFooters: n.parentNode = node
777 return node
778
779 def _iterateData(self, parentNode, submodel, data):
780 currentListItem = 0
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
787
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)
798
799
800 class KeyedList(List):
801 """
802 I am a widget which knows how to display the values stored within a
803 Python dictionary..
804
805 A KeyedList should be specified in the template HTML as so::
806
807 | <ul model="blah" view="KeyedList">
808 | <li pattern="emptyList">This will be displayed if the list is
809 | empty.</li>
810 | <li pattern="keyedListItem" view="Text">Foo</li>
811 | </ul>
812
813 I can take advantage of C{listHeader}, C{listFooter} and C{emptyList}
814 items just as a L{List} can.
815 """
816 def _iterateData(self, parentNode, submodel, data):
817 """
818 """
819 currentListItem = 0
820 keys = data.keys()
821 # Keys may be a tuple, if this is not a true dictionary but a dictionary -like object
822 if hasattr(keys, 'sort'):
823 keys.sort()
824 for key in keys:
825 newNode = self.getPattern('keyedListItem')
826 if not newNode:
827 newNode = self.getPattern('item', _RAISE)
828 if newNode:
829 warnings.warn("itemOf= is deprecated, "
830 "please use listItemOf instead",
831 DeprecationWarning)
832
833 appendModel(newNode, key)
834 if not newNode.attributes.get("view"):
835 newNode.attributes["view"] = "DefaultWidget"
836 parentNode.appendChild(newNode)
837
838
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
843 self.start = start
844 self.end = end
845
846 def setColumns(self, columns):
847 self.columns = columns
848
849 def setStart(self, start):
850 self.start = start
851
852 def setEnd(self, end):
853 self.end = end
854
855 def setUp(self, request, node, data):
856 pattern = self.getPattern('columnListRow', clone=0)
857 if self.end:
858 listSize = self.end - self.start
859 if listSize > len(data):
860 listSize = len(data)
861 else:
862 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)
868
869 newNode = self.getPattern('columnListItem')
870
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)
876 return node
877
878
879 class Bold(Widget):
880 tagName = 'b'
881
882
883 class Table(Widget):
884 tagName = 'table'
885
886
887 class Row(Widget):
888 tagName = 'tr'
889
890
891 class Cell(Widget):
892 tagName = 'td'
893
894
895 class RawText(Widget):
896 def generateDOM(self, request, node):
897 self.node = domhelpers.RawText(self.getData(request))
898 return self.node
899
900 from types import StringType
901
902 class Link(Widget):
903 """A utility class for generating <a href='foo'>bar</a> tags.
904 """
905 tagName = 'a'
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
911 else:
912 data = self.model
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)
917 self['href'] = lnk
918 node.tagName = self.tagName
919 domhelpers.clearNode(node)
920 node.appendChild(txt)
921
922 class RootRelativeLink(Link):
923 """
924 Just like a regular Link, only it makes the href relative to the
925 appRoot (that is, request.getRootURL()).
926 """
927 def setUp(self, request, node, data):
928 # hack, hack: some juggling so I can type less and share more
929 # code with Link
930 st = isinstance(data, StringType)
931 if st:
932 data = request.getRootURL() + '/' + data
933 Link.setUp(self, request, node, data)
934 if not st:
935 self['href'] = request.getRootURL() + '/' + self['href']
936
937 class ExpandMacro(Widget):
938 """A Macro expansion widget modeled after the METAL expander
939 in ZPT/TAL/METAL. Usage:
940
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::
944
945 def wvfactory_myMacro(self, request, node, model):
946 return ExpandMacro(
947 model,
948 macroFile="MyMacro.html",
949 macroName="main")
950
951 <div view="myMacro">
952 <span fill-slot="greeting">Hello</span>
953 <span fill-slot="greetee">World</span>
954 </div>
955
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::
959
960 <div macro="main">
961 <h3><span slot="greeting" />, <span slot="greetee" />!</h3>
962 </div>
963 """
964 def __init__(self, model, macroTemplate = "", macroFile="", macroFileDirecto ry="", macroName="", **kwargs):
965 self.macroTemplate = macroTemplate
966 self.macroFile=macroFile
967 self.macroFileDirectory=macroFileDirectory
968 self.macroName=macroName
969 Widget.__init__(self, model, **kwargs)
970
971 def generate(self, request, node):
972 if self.macroTemplate:
973 templ = view.View(
974 self.model,
975 template = self.macroTemplate).lookupTemplate(request)
976 else:
977 templ = view.View(
978 self.model,
979 templateFile=self.macroFile,
980 templateDirectory=self.macroFileDirectory).lookupTemplate(reques t)
981
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 `nod e'
984 macrolist = domhelpers.locateNodes(templ.childNodes, "macro", self.macro Name)
985 assert len(macrolist) == 1, ("No macro or more than "
986 "one macro named %s found." % self.macroName)
987
988 macro = macrolist[0]
989 del macro.attributes['macro']
990 slots = domhelpers.findElementsWithAttributeShallow(macro, "slot")
991 for slot in slots:
992 slotName = slot.attributes.get("slot")
993 fillerlist = domhelpers.locateNodes(node.childNodes, "fill-slot", sl otName)
994 assert len(fillerlist) <= 1, "More than one fill-slot found with nam e %s" % slotName
995 if len(fillerlist):
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)
1002
1003 return macro
1004
1005 class DeferredWidget(Widget):
1006 def setDataCallback(self, result, request, node):
1007 model = result
1008 view = None
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)
1013
1014 if view:
1015 view["id"] = self.attributes.get('id', '')
1016 view.templateNode = node
1017 view.controller = self.controller
1018 return view.setDataCallback(result, request, node)
1019 else:
1020 return Widget.setDataCallback(self, result, request, node)
1021
1022
1023 class Break(Widget):
1024 """Break into pdb when this widget is rendered. Mildly
1025 useful for debugging template structure, model stacks,
1026 etc.
1027 """
1028 def setUp(self, request, node, data):
1029 import pdb; pdb.set_trace()
1030
1031
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)
OLDNEW
« no previous file with comments | « third_party/twisted_8_1/twisted/web/woven/view.py ('k') | third_party/twisted_8_1/twisted/web/xmlrpc.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698