Package turbogears :: Package widgets :: Module base

Source Code for Module turbogears.widgets.base

   1  """Base TurboGears widget implementation""" 
   2   
   3  __all__ = ['load_widgets', 'all_widgets', 'Widget', 'CompoundWidget', 
   4      'WidgetsList', 'register_static_directory', 'static', 
   5      'Resource', 'Link', 'CSSLink', 'JSLink', 
   6      'Source', 'CSSSource', 'JSSource', 
   7      'js_location', 'mochikit', 'jsi18nwidget', 
   8      'WidgetDescription', 'set_with_self'] 
   9   
  10  import os 
  11  import itertools 
  12  import warnings 
  13   
  14  import pkg_resources 
  15   
  16  from cherrypy import request 
  17  import tgmochikit 
  18   
  19  from turbogears import config, startup, view 
  20  from turbogears.util import (setlike, to_unicode, copy_if_mutable, 
  21      get_package_name, request_available) 
  22  from turbogears.i18n.utils import get_locale 
  23  from turbogears.widgets.meta import MetaWidget, load_template 
24 25 26 -class Enum(set):
27 """Enum used at js_locations. 28 29 This is less strict than ``turbogears.utils.Enum`` and serves our 30 purposes as well as allowing any object with ``retrieve_javascript``, 31 ``retrieve_css``, and ``location`` attributes to provide resources to 32 the template when scanned in ``turbogears.controllers._process_output``. 33 34 Example: 35 36 >>> locations = Enum('bodytop', 'bodybottom', head') 37 >>> locations.head == locations.head 38 True 39 >>> locations.head == locations.bodybottom 40 False 41 >>> locations.head in locations 42 True 43 >>> locations.foo 44 Traceback (most recent call last): 45 ... 46 AttributeError 47 48 """ 49
50 - def __init__(self, *args):
51 set.__init__(self, args)
52
53 - def __getattr__(self, name):
54 if name in self: 55 return name 56 raise AttributeError
57
58 - def __repr__(self):
59 return "%s(%s)" % (self.__class__.__name__, ', '.join(map(repr, self)))
60 61 62 # Set up a counter for every declared widget in a WidgetsList instance, 63 # so their order is preserved. 64 counter = itertools.count()
65 66 67 -def load_widgets():
68 """Load all widgets provided by the widget entry point.""" 69 for widget_mod in pkg_resources.iter_entry_points('turbogears.widgets'): 70 try: 71 widget_mod.load() 72 except Exception, e: 73 raise ImportError('Error loading plugin "%s"\n%s: %s' 74 % (widget_mod, e.__class__.__name__, e))
75 76 all_widgets = set()
77 78 79 ############################################################################# 80 # Widgets base classes # 81 ############################################################################# 82 83 -class Widget(object):
84 """A TurboGears Widget. 85 86 '__init__' and 'update_params' are the only methods you might need to 87 care extending. 88 89 Attributes you should know about: 90 91 name: The name of the widget. 92 template: The Genshi or Kid template for the widget. 93 engine_name: The name of the templating engine to be used for 94 rendering the template. You can also specify the engine 95 by adding a 'genshi:' or 'kid:' prefix to the template. 96 If the engine is not specified, and cannot be derived 97 from a xmlns:py attribute in the template source, then 98 the global default_engine ('genshi') will be used. 99 default: Default value for the widget. 100 css: List of CSSLinks and CSSSources for the widget. These 101 will be automatically pulled and inserted in the 102 page template that displays it. 103 javascript: List of JSLinks and JSSources for the widget. Same as css. 104 is_named: A property returning True if the widget has overridden its 105 default name. 106 params: All parameter names listed here will be treated as special 107 parameters. This list is updated by the metaclass and 108 always contains *all* params from the widget itself and 109 all its bases. Can be used as a quick reminder of all 110 the params your widget has at its disposal. They all 111 behave the same and have the same priorities regarding their 112 overridal. Read on... 113 params_doc: A dictionary containing 'params' names as keys and their 114 docstring as value. For documentation at the widget browser. 115 116 All initialization parameters listed at the class attribute "params" can be 117 defined as class attributes, overridden at __init__ or at display time. 118 They will be treated as special params for the widget, which means: 119 120 1) You can fix default values for them when subclassing Widget. 121 122 2) If passed as **params to the constructor, the will be bound automatically 123 to the widget instance, taking preference over any class attributes 124 previously defined. Mutable attributes (dicts and lists) defined as class 125 attributes are safe to modify as care is taken to copy them so the class 126 attribute remains unchanged. 127 128 3) They can be further overriden by passing them as keyword arguments to the 129 display() method. This will only affect that display() method call in a 130 thread-safe way. 131 132 4) A callable can be passed and it will be called automatically when sending 133 variables to the template. This can be handy to pick up parameters which 134 change in every request or affect many widgets simultaneously. 135 136 """ 137 138 __metaclass__ = MetaWidget 139 140 name = "widget" 141 template = None 142 engine_name = None 143 default = None 144 css = [] 145 javascript = [] 146 params = [] 147 params_doc = {} 148
149 - def __init__(self, name=None, template=None, engine_name=None, 150 default=None, **params):
151 """Widget initialization. 152 153 All initialization has to take place in this method. 154 It's not thread-safe to mutate widget's attributes outside this method 155 or anytime after widget's first display. 156 157 *Must* call super(MyWidget, self).__init__(*args, **kw) cooperatively, 158 unless, of course, you know what you're doing. Preferably this should 159 be done before any actual work is done in the method. 160 161 Parameters: 162 163 name: The widget's name. In input widgets, this will also be the 164 name of the variable that the form will send to the 165 controller. This is the only param that is safe to pass as a 166 positional argument to __init__. 167 template: The template that the widget should use to display itself. 168 Currently only Genshi and Kid templates are supported. You 169 can both initialize with a template string or with the path 170 to a file-base template: 'myapp.templates.widget_tmpl' 171 engine_name: The engine to be used for rendering the template, if not 172 specified in the template already. 173 default: Default value to display when no value is passed at display 174 time. 175 **params: Keyword arguments specific to your widget or to any of its 176 bases. If listed at class attribute 'params' the will be 177 bound automatically to the widget instance. 178 179 Note: Do not confuse these parameters with parameters listed at 180 "params". Some widgets accept parameters at the constructor which are 181 not listed params, these parameter won't be passed to the template, be 182 automatically called, etc. 183 184 """ 185 self._declaration_counter = counter.next() 186 187 if name: 188 self.name = name 189 if engine_name and engine_name != self.engine_name: 190 if self.template and not template: 191 # load the template again with a different engine 192 template = self.template 193 self.engine_name = engine_name 194 if template: 195 self.template_c, self.template, self.engine_name = load_template( 196 template, self.engine_name) 197 if default is not None: 198 self.default = default 199 200 # logic for managing the params attribute 201 for param in self.__class__.params: 202 if param in params: 203 # make sure we don't keep references to mutables 204 setattr(self, param, copy_if_mutable(params.pop(param))) 205 else: 206 # make sure we don't alter mutable class attributes 207 value, mutable = copy_if_mutable( 208 getattr(self.__class__, param), True) 209 if mutable: 210 # re-set it only if mutable 211 setattr(self, param, value) 212 213 for unused in params.iterkeys(): 214 warnings.warn('keyword argument "%s" is unused at %r instance' % ( 215 unused, self.__class__.__name__))
216
217 - def adjust_value(self, value, **params):
218 """Adjust the value sent to the template on display.""" 219 return value
220
221 - def update_params(self, params):
222 """Update the template parameters. 223 224 This method will have the last chance to update the variables sent to 225 the template for the specific request. All parameters listed at class 226 attribute 'params' will be available at the 'params' dict this method 227 receives. 228 229 *Must* call super(MyWidget, self).update_params(params) cooperatively, 230 unless, of course, your know what you're doing. Preferably this should 231 be done before any actual work is done in the method. 232 233 """ 234 pass
235 236 # The following methods are needed to be a well behaved widget, however, 237 # there is rarely the need to override or extend them if inheriting 238 # directly or indirectly from Widget. 239
240 - def __call__(self, *args, **params):
241 """Delegate to display. Used as an alias to avoid tiresome typing.""" 242 return self.display(*args, **params)
243
244 - def _prepare_params(self, value, params):
245 """Prepare the widget params for rendering the template.""" 246 for param in self.__class__.params: 247 if param in params: 248 param_value = params[param] 249 if callable(param_value): 250 param_value = param_value() 251 else: 252 # if the param hasn't been overridden (passed as a keyword 253 # argument inside **params) put the corresponding instance 254 # value inside params. 255 param_value = getattr(self, param, None) 256 # make sure we don't pass a reference to mutables 257 params[param] = copy_if_mutable(param_value) 258 if not params.get('name'): 259 params['name'] = self.name 260 if value is None: 261 value = self.default 262 if callable(value): 263 value = value() 264 params['value'] = to_unicode(self.adjust_value(value, **params)) 265 self.update_params(params)
266
267 - def display(self, value=None, **params):
268 """Display the widget in a Genshi or Kid template. 269 270 Returns a Genshi Markup stream or an ElementTree node instance, 271 depending on the template in which the widget shall be displayed. 272 If you need serialized output in a string, call 'render' instead. 273 274 Probably you will not need to override or extend if inheriting from 275 Widget. 276 277 @params: 278 279 value : The value to display in the widget. 280 **params: Extra parameters specific to the widget. All keyword params 281 supplied will pass through the update_params method which 282 will have a last chance to modify them before reaching the 283 template. 284 285 """ 286 engine_name = self.engine_name 287 if not getattr(self, 'template_c', False): 288 warnings.warn(engine_name in view.engines 289 and "Widget instance %r has no template defined" % self 290 or "Trying to render a widget, but the %s templating engine" 291 " is not installed or not yet loaded." % engine_name) 292 return None 293 self._prepare_params(value, params) 294 try: 295 transform = view.engines[engine_name].transform 296 except (KeyError, AttributeError): 297 # this can happen if you render a widget before application startup 298 # when view.load_engines() has not yet been called 299 raise RuntimeError( 300 "Trying to render a widget, but the %s templating engine" 301 " is not installed or not yet loaded." % engine_name) 302 303 # If the widget template is Kid, but the page template is Genshi, 304 # we need to keep track of the nesting level, because Genshi cannot 305 # display Kid's ElementTree elements directly. 306 try: 307 engine_names = request.tg_template_engine_names 308 except AttributeError: 309 request.tg_template_engine_names = engine_names = [] 310 engine_names.append(engine_name) 311 try: 312 output = transform(params, self.template_c) 313 # convert output when switching between templating engines 314 # and make sure parameters are evaluated on time in Genshi 315 # (operating with streams doesn't work well with the path 316 # acrobatics performed for properly handling nested form fields) 317 if engine_names: 318 adapter = { 319 'kid-genshi': view.kid_xml, 320 'genshi-kid': view.genshi_et, 321 'genshi-genshi': list 322 }.get('-'.join(engine_names[-2:])) 323 if adapter: 324 output = adapter(output) 325 finally: 326 if engine_names: 327 engine_names.pop() 328 329 return output
330
331 - def render(self, value=None, format='html', **params):
332 """Exactly the same as display() but return serialized output instead. 333 334 Useful for debugging or to display the widget in a template other 335 than Genshi or Kid, like Cheetah, Mako, Nevow Stan, ... 336 337 """ 338 engine_name = self.engine_name 339 if not getattr(self, 'template_c', False): 340 warnings.warn(engine_name in view.engines 341 and "Widget instance %r has no template defined" % self 342 or "Trying to render a widget, but the %s templating engine" 343 " is not installed or not yet loaded." % engine_name) 344 return None 345 self._prepare_params(value, params) 346 try: 347 render = view.engines[engine_name].render 348 except (KeyError, AttributeError): 349 # this can happen if you render a widget before application startup 350 # when view.load_engines() has not yet been called 351 raise RuntimeError( 352 "Trying to render a widget, but the %s templating engine" 353 " is not installed or not yet loaded." % engine_name) 354 355 try: 356 engine_names = request.tg_template_engine_names 357 except AttributeError: 358 request.tg_template_engine_names = engine_names = [] 359 engine_names.append(engine_name) 360 361 try: 362 output = render(params, 363 format=format, fragment=True, template=self.template_c) 364 finally: 365 if engine_names: 366 engine_names.pop() 367 368 return output
369
370 - def retrieve_javascript(self):
371 """Return the needed JavaScript resources. 372 373 Return a setlike instance containing all the JSLinks and JSSources 374 the widget needs. 375 376 """ 377 scripts = setlike() 378 for script in self.javascript: 379 scripts.add(script) 380 return scripts
381
382 - def retrieve_css(self):
383 """Return the needed CSS resources. 384 385 Return a setlike instance with all the CSSLinks and CSSSources 386 the widget needs. 387 388 """ 389 css = setlike() 390 for cssitem in self.css: 391 css.add(cssitem) 392 return css
393 394 @property
395 - def is_named(self):
396 """Return True if the widget has overridden its default name.""" 397 return self.name != "widget"
398
399 - def __setattr__(self, key, value):
400 if self._locked: 401 raise ValueError( 402 "It is not threadsafe to modify widgets in a request") 403 else: 404 return super(Widget, self).__setattr__(key, value)
405
406 - def __repr__(self):
407 return "%s(%s)" % (self.__class__.__name__, ', '.join( 408 ["%s=%r" % (var, getattr(self, var)) 409 for var in ['name'] + self.__class__.params]))
410
411 412 -class CompoundWidget(Widget):
413 """A widget that can contain other widgets. 414 415 A compound widget is a widget that can group several widgets to make a 416 complex widget. Child widget names must be listed at their widget's 417 ``member_widgets`` attribute. 418 419 """ 420 421 compound = True 422 member_widgets = [] 423
424 - def __init__(self, *args, **kw):
425 # logic for managing the member_widgets attribute 426 for member in self.__class__.member_widgets: 427 if member in kw: 428 setattr(self, member, kw.pop(member)) 429 elif not hasattr(self, member): 430 setattr(self, member, None) 431 super(CompoundWidget, self).__init__(*args, **kw)
432
433 - def iter_member_widgets(self):
434 """Iterates over all the widget's children""" 435 for member in self.__class__.member_widgets: 436 attr = getattr(self, member, None) 437 if isinstance(attr, list): 438 for widget in attr: 439 yield widget 440 elif attr is not None: 441 yield attr
442
443 - def display(self, value=None, **params):
444 params["member_widgets_params"] = params.copy() 445 # logic for managing the member_widgets attribute 446 for member in self.__class__.member_widgets: 447 params[member] = getattr(self, member, None) 448 return super(CompoundWidget, self).display(value, **params)
449
450 - def render(self, value=None, format='html', **params):
451 params["member_widgets_params"] = params.copy() 452 # logic for managing the member_widgets attribute 453 for member in self.__class__.member_widgets: 454 params[member] = getattr(self, member, None) 455 return super(CompoundWidget, self).render(value, format, **params)
456
457 - def update_params(self, d):
458 super(CompoundWidget, self).update_params(d) 459 d['value_for'] = lambda f: self.value_for(f, d['value']) 460 widgets_params = d['member_widgets_params'] 461 d['params_for'] = lambda f: self.params_for(f, **widgets_params)
462
463 - def value_for(self, item, value):
464 """Get value for member widget. 465 466 Pick up the value for a given member_widget 'item' from the 467 value dict passed to this widget. 468 469 """ 470 name = getattr(item, "name", item) 471 if isinstance(value, dict): 472 return value.get(name) 473 else: 474 return None
475
476 - def params_for(self, item, **params):
477 """Get params for member widget. 478 479 Pick up the params for the given member_widget 'item' from 480 the params dict passed to this widget. 481 482 """ 483 name = getattr(item, "name", item) 484 item_params = {} 485 for k, v in params.iteritems(): 486 if isinstance(v, dict): 487 if name in v: 488 item_params[k] = v[name] 489 return item_params
490
491 - def retrieve_javascript(self):
492 """Get JavaScript for the member widgets. 493 494 Retrieve the JavaScript for all the member widgets and 495 get an ordered union of them. 496 497 """ 498 scripts = setlike() 499 for script in self.javascript: 500 scripts.add(script) 501 for widget in self.iter_member_widgets(): 502 for script in widget.retrieve_javascript(): 503 scripts.add(script) 504 return scripts
505
506 - def retrieve_css(self):
507 """Get CSS for the member widgets. 508 509 Retrieve the CSS for all the member widgets and 510 get an ordered union of them. 511 512 """ 513 css = setlike() 514 for cssitem in self.css: 515 css.add(cssitem) 516 for widget in self.iter_member_widgets(): 517 for cssitem in widget.retrieve_css(): 518 css.add(cssitem) 519 return css
520
521 522 ############################################################################# 523 # Declarative widgets support # 524 ############################################################################# 525 526 -class MetaWidgetsList(type):
527 """Metaclass for WidgetLists. 528 529 Takes care that the resulting WidgetList has all widgets in the same order 530 as they were declared. 531 532 """ 533
534 - def __new__(mcs, class_name, bases, class_dict):
535 declared_widgets = [] 536 for base in bases: 537 declared_widgets.extend(getattr(base, 'declared_widgets', [])) 538 for name, value in class_dict.items(): 539 if isinstance(value, Widget): 540 if not value.is_named: 541 value.name = name 542 declared_widgets.append(value) 543 # we keep the widget accessible as attribute here, 544 # i.e. we don't del class_dict[name] as we did before 545 declared_widgets.sort(key=lambda w: w._declaration_counter) 546 cls = type.__new__(mcs, class_name, bases, class_dict) 547 cls.declared_widgets = declared_widgets 548 return cls
549
550 551 -class WidgetsList(list):
552 """A widget list. 553 554 That's really all. A plain old list that you can declare as a classs 555 with widgets ordered as attributes. Syntactic sugar for an unsweet world. 556 557 """ 558 559 __metaclass__ = MetaWidgetsList 560
561 - def __init__(self, *args):
562 super(WidgetsList, self).__init__(self.declared_widgets) 563 if args: 564 if len(args) == 1: 565 args = args[0] 566 if isinstance(args, Widget): 567 args = [args] 568 self.extend(args) 569 if not self: 570 warnings.warn("You have declared an empty WidgetsList")
571 572 # Make WidgetsList instances usable as Widget containers in the dict 573 # returned by controller methods, so that the JS/CSS resources of all 574 # contained widgets get picked up by the templates: 575
576 - def retrieve_javascript(self):
577 return itertools.chain(*(w.retrieve_javascript() for w in self 578 if callable(getattr(w, 'retrieve_javascript', None))))
579
580 - def retrieve_css(self):
581 return itertools.chain(*(w.retrieve_css() for w in self 582 if callable(getattr(w, 'retrieve_css', None))))
583
584 585 ############################################################################# 586 # CSS, JS and mochikit stuff # 587 ############################################################################# 588 589 -def register_static_directory(modulename, directory):
590 """Set up a static directory for JavaScript and CSS files. 591 592 You can refer to this static directory in templates as 593 ${tg.widgets}/modulename 594 595 """ 596 directory = os.path.abspath(directory) 597 config.update({'/tg_widgets/%s' % modulename: { 598 'tools.staticdir.on' : True, 599 'tools.staticdir.dir': directory 600 }})
601 602 static = "turbogears.widgets" 603 604 register_static_directory(static, 605 pkg_resources.resource_filename(__name__, 'static')) 606 607 register_static_directory('turbogears', 608 pkg_resources.resource_filename('turbogears', 'static'))
609 610 611 -def set_with_self(self):
612 theset = setlike() 613 theset.add(self) 614 return theset
615
616 617 -class Resource(Widget):
618 """A resource for your widget. 619 620 For example, this can be a link to an external JavaScript/CSS file 621 or inline source to include at the template the widget is displayed. 622 623 """ 624 order = 0 625 params = ['order'] 626 params_doc = { 627 'order': "JS and CSS are sorted in this 'order' at render time" 628 }
629 649 665 666 667 js_location = Enum('head', 'bodytop', 'bodybottom') 696
697 698 -class TGMochiKit(JSLink):
699 """This JSLink includes MochkKit by means of the tgMochiKit project. 700 701 Depending on three config options, you can decide 702 703 ``tg_mochikit.version`` -- ``"1.3.1"`` 704 Which version of MochiKit should be used. 705 ``tg_mochikit.packed`` -- ``False`` 706 Whether to deliver the MochiKit JS code packed or unpacked. 707 ``tg_mochikit.xhtml`` -- ``False`` 708 Whether to be XHTML-compliant or not when ``tg_mochikit.packed`` is 709 ``False``, i.e. selects whether the ``turbogears.mochikit`` widget 710 should include all of MochiKit's submodules as separate JavaScript 711 resources or just the main ``MochiKit.js`` file. 712 713 For more explanation of the options, see the tgMochiKit documentation at 714 715 http://docs.turbogears.org/tgMochiKit 716 717 """ 718 719 template = """<script xmlns:py="http://genshi.edgewall.org/" 720 py:for="js in javascripts" py:replace="js.display()" 721 /> 722 """ 723
724 - def retrieve_javascript(self):
725 tgmochikit.init(register_static_directory, config) 726 if config.get('tg.mochikit_suppress', False): 727 if 'turbogears.mochikit' in config.get('tg.include_widgets', []): 728 warnings.warn("""\ 729 tg.mochikit_suppress == True, but 'turbogears.mochikit' is to be included via 730 'tg.include_widgets'. This is a contradiction, and mochikit_suppress overrides 731 the inclusion to prevent subtle errors due to version mixing of MochiKit.""") 732 return [] 733 javascripts = [JSLink('tgmochikit', path) 734 for path in tgmochikit.get_paths()] 735 return javascripts
736
737 - def update_params(self, d):
738 super(TGMochiKit, self).update_params(d) 739 d['javascripts'] = self.retrieve_javascript()
740 741 mochikit = TGMochiKit('tgmochikit')
742 743 744 -class JSI18NWidget(Widget):
745 """JavaScript i18n support widget. 746 747 This is a widget that can be used to add support for 748 internationalization of JavaScript texts. 749 750 It will basically add an implementation of the 751 _ function that then can be used to wrap text literals 752 in javascript-code. 753 754 Additionally, message-catalogs are looked up and included 755 depending on the current locale. 756 757 To include the widget, put 758 759 tg.include_widgets = ['turbogears.jsi18nwidget'] 760 761 in your application configuration. 762 763 """ 764 765 params = ['locale_catalog_providers'] 766 locale_catalog_providers = [] 767
768 - def __init__(self, *args, **kwargs):
769 super(JSI18NWidget, self).__init__(*args, **kwargs) 770 self._initialized = False
771
772 - def register_package_provider(self, 773 package=None, directory=None, domain=None):
774 775 parent = self 776 777 class PackageProvider(object): 778 def __init__(self): 779 self._initialized = False
780 781 def __call__(self, locale): 782 if not self._initialized: 783 # the leading underscore is to prevent shadowing of the 784 # register_package_provider-arguments 785 if package is None: 786 _package = get_package_name() 787 else: 788 _package = package 789 if directory is None: 790 _directory = "static/javascript" 791 else: 792 _directory = directory 793 if domain is None: 794 _domain = config.get('i18n.domain', 'messages') 795 else: 796 _domain = domain 797 js_dir = pkg_resources.resource_filename( 798 _package, _directory) 799 register_static_directory(_package, js_dir) 800 self._package_name = _package 801 self._domain = _domain 802 self._initialized = True 803 js = [] 804 for loc in locale, locale[:2]: 805 link = JSLink( 806 self._package_name, "%s-%s.js" % (self._domain, loc)) 807 if parent.linked_file_exists(link): 808 js.append(link) 809 break 810 return js
811 812 self.locale_catalog_providers.append(PackageProvider()) 813
814 - def retrieve_javascript(self):
815 if not self._initialized: 816 if not config.get('i18n.suppress_default_package_provider', False): 817 self.register_package_provider() 818 self._initialized = True 819 js = super(JSI18NWidget, self).retrieve_javascript() 820 js.add(JSLink('turbogears', 'js/i18n_base.js')) 821 locale = get_locale() 822 for pp in self.locale_catalog_providers: 823 js.extend(pp(locale)) 824 return js
825
826 - def linked_file_exists(self, widget):
827 widget_path = config.app.get('/tg_widgets/%s' % widget.mod) 828 if widget_path: 829 static_dir = widget_path.get('tools.staticdir.dir') 830 if static_dir: 831 return os.path.exists(os.path.join(static_dir, widget.name)) 832 return False
833 834 jsi18nwidget = JSI18NWidget()
835 836 837 -class Source(Resource):
838 839 params = ["src"] 840
841 - def __init__(self, src, *args, **kw):
842 super(Source, self).__init__(*args, **kw) 843 self.src = src
844
845 - def __hash__(self):
846 return hash(self.src)
847
848 - def __eq__(self, other):
849 return self.src == getattr(other, "src", None)
850
851 852 -class CSSSource(Source):
853 """A CSS source snippet.""" 854 855 template = """ 856 <style type="text/css" media="$media">$src</style> 857 """ 858 params = ["media"] 859 params_doc = {'src': 'The CSS source for the link', 860 'media' : 'Specify the media the css source link is for'} 861 media = "all" 862 retrieve_css = set_with_self
863
864 865 -class JSSource(Source):
866 """A JavaScript source snippet.""" 867 868 template = """ 869 <script type="text/javascript" 870 defer="${defer and 'defer' or None}">$src</script> 871 """ 872 params = ["defer"] 873 params_doc = {'defer': 'If true, browser may defer execution of the script'} 874 defer = False 875 location = js_location.head 876
877 - def __init__(self, src, location=None, **kw):
878 if location: 879 if location not in js_location: 880 raise ValueError( 881 "JSSource location should be in %s" % js_location) 882 self.location = location 883 super(JSSource, self).__init__(src, **kw)
884 885 retrieve_javascript = set_with_self
886
887 888 ############################################################################# 889 # Classes for supporting the toolbox widget browser # 890 ############################################################################# 891 892 -class MetaDescription(MetaWidget):
893 """Metaclass for widget descriptions. 894 895 Makes sure the widget browser knows about all of them as soon as they 896 come into existence. 897 898 """ 899
900 - def __init__(mcs, name, bases, dct):
901 super(MetaDescription, mcs).__init__(name, bases, dct) 902 register = dct.get('register', True) 903 if name != 'WidgetDescription' and register: 904 all_widgets.add(mcs)
905
906 907 -class WidgetDescription(CompoundWidget):
908 """A description for a Widget. 909 910 Makes the 'for_widget' widget appear in the browser. It's a nice way to 911 show off to your friends your coolest new widgets and to have a testing 912 platform while developing them. 913 914 """ 915 916 __metaclass__ = MetaDescription 917 918 template = """ 919 <div xmlns:py="http://genshi.edgewall.org/" 920 py:content="for_widget.display()"/> 921 """ 922 for_widget = None 923 member_widgets = ["for_widget"] 924 show_separately = False 925 926 @property
927 - def name(self):
928 return self.for_widget_class.__name__
929 930 @property
931 - def for_widget_class(self):
932 return self.for_widget.__class__
933 934 @property
935 - def description(self):
936 return self.for_widget_class.__doc__
937 938 @property
939 - def full_class_name(self):
940 cls = self.for_widget_class 941 return "%s.%s" % (cls.__module__, cls.__name__)
942 943 @property
944 - def source(self):
945 import inspect 946 return inspect.getsource(self.__class__)
947
948 - def retrieve_css(self):
949 return self.for_widget.retrieve_css()
950
951 - def retrieve_javascript(self):
952 return self.for_widget.retrieve_javascript()
953
954 955 -class CoreWD(WidgetDescription):
956 957 register = False 958 959 @property
960 - def full_class_name(self):
961 cls = self.for_widget_class 962 return "turbogears.widgets.%s" % (cls.__name__)
963
964 965 -class RenderOnlyWD(WidgetDescription):
966 967 register = False 968 template = """ 969 <div> 970 This widget will render like that:<br/><br/> 971 <tt class="rendered">${for_widget.render(value)}</tt> 972 </div> 973 """ 974
975 - def retrieve_javascript(self):
976 return setlike()
977
978 - def retrieve_css(self):
979 return setlike()
980
981 982 ############################################################################# 983 # CSS and JS WidgetDescription's # 984 ############################################################################# 985 986 -class CSSLinkDesc(CoreWD, RenderOnlyWD):
987 988 name = "CSS Link" 989 for_widget = CSSLink('turbogears', 'css/yourstyle.css')
990
991 992 -class JSLinkDesc(CoreWD, RenderOnlyWD):
993 994 name = "JS Link" 995 for_widget = JSLink('turbogears', 'js/yourscript.js')
996
997 998 -class CSSSourceDesc(CoreWD, RenderOnlyWD):
999 1000 name = "CSS Source" 1001 for_widget = CSSSource("body { font-size:12px; }")
1002
1003 1004 -class JSSourceDesc(CoreWD, RenderOnlyWD):
1005 1006 name = "JS Source" 1007 for_widget = JSSource("document.title = 'Hello World';")
1008