class SubDocumentsList(RepeatingWidget): template = 'sprox.widgets.tw2widgets.templates.subdocuments' child = SubDocument children_attrs = Param('children_attrs', attribute=False, default={}) direct = Param('direct', attribute=False, default=False) extra_reps = 1 @classmethod def post_define(cls): if not cls.css_class: cls.css_class = '' if 'subdocuments' not in cls.css_class: cls.css_class += ' subdocuments' cls.child.children_attrs = cls.children_attrs if cls.direct: cls.child.direct = True def prepare(self): super(SubDocumentsList, self).prepare() # Hide the last element, used to add new entries self.children[len(self.children) - 1].attrs['style'] = 'display: none' def _validate(self, value, state=None): return super(SubDocumentsList, self)._validate(StripBlanks().to_python(value, state), state)
class ParallaxImageWidget(Widget): resources = [jquery_js, parallaxjs_js, workaround_js] template = "tw2.jqplugins.parallaxjs.templates.parallax_image" image_src = Param( "The image to be shown with the parallax effect.", attribute=True, view_name='data-image-src', ) min_height = Param("The minimum height of the outer div.", default=None) overlay_text = Param("Optional text to be overlayed over the image", default=None) def prepare(self): super(ParallaxImageWidget, self).prepare() self.safe_modify('attrs') s_parts = ( "background:transparent", "min-height:" + self.min_height if self.min_height else None, self.attrs.get('style')) self.attrs['style'] = ";".join(x for x in s_parts if x) @classmethod def inject_resources(cls): for res in cls.resources: res.inject()
class AjaxManagePhotos(RactiveWidget): action = Param('Url used to save newly uploaded photos', request_local=False) delete_action = Param('Url used to delete uploaded photos', request_local=False) permit_upload = Param('Whenever to enable upoad of new photos or only replace existing', request_local=False, default=True) ractive_params = ['action', 'delete_action', 'permit_upload', 'css_class', 'id'] ractive_template = ''' <div id="{{id}}" class="{{css_class ? css_class : ''}}"> {{#photos}} <div class="photo" intro="fade"> {{#delete_action}} <div class="photo-delete" on-tap="request_delete">⨯</div> {{/delete_action}} <div class="photo-picture photo-for-edit" data-photo-uid="{{uid}}" on-tap="request_upload"> <img src="{{url}}" data-image-url="{{full_url}}"/> </div> <div class="photo-picture photo-for-modal" data-photo-uid="{{uid}}"> <img src="{{url}}" data-image-url="{{full_url}}"/> </div> {{#loading}} <div class='loading-photo-div'></div> {{/loading}} </div> {{/photos}} {{#permit_upload}} <div class="photo-upload btn btn-primary" on-tap="request_upload" style="margin: 12px 0 0 10px;">carica foto</div> {{/permit_upload}} </div> ''' ractive_init = '''
class SubDocument(ListLayout): direct = Param('direct', attribute=False, default=False) children_attrs = Param('children_attrs', attribute=False, default={}) @classmethod def post_define(cls): if not cls.css_class: cls.css_class = '' if 'subdocument' not in cls.css_class: cls.css_class += ' subdocument' for c in getattr(cls, 'children', []): # SubDocument always propagates its template to nested subdocuments if issubclass(c, SubDocument): c.children_attrs = cls.children_attrs c.template = cls.template elif issubclass(c, SubDocumentsList): c.children_attrs = cls.children_attrs c.child = c.child(template=cls.template) else: for name, value in cls.children_attrs.items(): setattr(c, name, value) if cls.direct: # In direct mode we just act as a proxy to the real field # so we set the field key equal to our own c.compound_key = ':'.join(c.compound_key.split(':')[:-1]) if not hasattr(cls, 'children'): cls.children = [] @tw2v.catch_errors def _validate(self, value, state=None): if self.direct: # In direct mode we just act as a proxy to the real field # so we directly validate the field return self.children[0]._validate(value, state) else: return super(SubDocument, self)._validate(value, state) def prepare(self): if self.direct: # In direct mode we just act as a proxy to the real field # so we provide the value to the field and throw away error messages # to avoid duplicating them (one for us and one for the field). self.children[0].value = self.value self.error_msg = '' super(SubDocument, self).prepare()
class PropertySingleSelectField(SingleSelectField): entity = Param('entity', attribute=False, default=None) provider = Param('provider', attribute=False, default=None) field_name = Param('field_name', attribute=False, default=None) dropdown_field_names = Param('dropdown_field_names', attribute=False, default=None) nullable = Param('nullable', attribute=False, default=False) disabled = Param('disabled', attribute=False, default=False) prompt_text = None def prepare(self): #This is required for ming entity = self.__class__.entity options = self.provider.get_dropdown_options(entity, self.field_name, self.dropdown_field_names) self.options = [(unicode_text(k), unicode_text(v)) for k, v in options] if self.nullable: self.options.append(['', "-----------"]) if not self.value: self.value = '' self.value = unicode_text(self.value) super(PropertySingleSelectField, self).prepare()
class PropertyMultipleSelectField(MultipleSelectField): entity = Param('entity', attribute=False, default=None) provider = Param('provider', attribute=False, default=None) field_name = Param('field_name', attribute=False, default=None) dropdown_field_names = Param('dropdown_field_names', attribute=False, default=None) nullable = Param('nullable', attribute=False, default=False) disabled = Param('disabled', attribute=False, default=False) def _safe_from_validate(self, validator, value, state=None): try: return validator.from_python(value, state=state) except Invalid: return Invalid def _safe_to_validate(self, validator, value, state=None): try: return validator.to_python(value, state=state) except Invalid: return Invalid def _validate(self, value, state=None): # Work around a bug in tw2.core <= 2.2.2 where twc.safe_validate # doesn't work with formencode validators. value = value or [] if not isinstance(value, (list, tuple)): value = [value] if self.validator: self.validator.to_python(value, state) if self.item_validator: value = [ self._safe_to_validate(self.item_validator, v) for v in value ] self.value = [v for v in value if v is not Invalid] return self.value def prepare(self): # This is required for ming entity = self.__class__.entity options = self.provider.get_dropdown_options(entity, self.field_name, self.dropdown_field_names) self.options = [(unicode_text(k), unicode_text(v)) for k, v in options] if not self.value: self.value = [] if not hasattr(self, '_validated') and self.item_validator: self.value = [ self._safe_from_validate(self.item_validator, v) for v in self.value ] self.value = [unicode_text(v) for v in self.value if v is not Invalid] super(PropertyMultipleSelectField, self).prepare()
class Lightbox(Widget): resources = [jquery_js, lightbox_js, lightbox_css, static_resources] options = Param("Dictionary with options to the Lightbox widget.", default=None) template = "" def prepare(self): super(Lightbox, self).prepare() copy_img_alt_src = """jQuery('a[data-lightbox]:not([data-title])>img[alt]:only-child').each( function(i, elem) { elem.parentElement.setAttribute( 'data-title', elem.getAttribute('alt')); });""" self.add_call(copy_img_alt_src) if self.options: self.add_call(js_function("lightbox.option")(self.options)) def generate_output(self, displays_on): return ""
class PropertyMultipleSelectField(MultipleSelectField): entity = Param('entity', attribute=False, default=None) provider = Param('provider', attribute=False, default=None) field_name = Param('field_name', attribute=False, default=None) dropdown_field_names = Param('dropdown_field_names', attribute=False, default=None) nullable = Param('nullable', attribute=False, default=False) disabled = Param('disabled', attribute=False, default=False) def _safe_validate(self, validator, value, state=None): try: value = validator.to_python(value, state=state) validator.validate_python(value, state=state) return value except Invalid: return Invalid def _validate(self, value, state=None): value = value or [] if not isinstance(value, (list, tuple)): value = [value] if self.validator: value = [self._safe_validate(self.validator, v) for v in value] self.value = [v for v in value if v is not Invalid] return self.value def prepare(self): #This is required for ming entity = self.__class__.entity options = self.provider.get_dropdown_options(entity, self.field_name, self.dropdown_field_names) self.options = [(unicode_text(k), unicode_text(v)) for k, v in options] if not self.value: self.value = [] self.value = [unicode_text(v) for v in self.value] super(PropertyMultipleSelectField, self).prepare()
class TableDefWidget(Widget): template = "genshi:sprox.widgets.tw2widgets.templates.tableDef" identifier = Param('identifier', attribute=False)
class RecordFieldWidget(Widget): template = "genshi:sprox.widgets.tw2widgets.templates.recordField" field_name = Param('field_name', attribute=False) css_class = "recordfieldwidget"
class RecordViewWidget(Widget): template = "genshi:sprox.widgets.tw2widgets.templates.recordViewTable" entity = Param('entity', attribute=False, default=None)
class EntityLabelWidget(Widget): template = "genshi:sprox.widgets.tw2widgets.templates.entityLabel" controller = Param('controller', attribute=False, default=None) entity = Param('entity', attribute=False) css_class = "entitylabelwidget"
class ModelLabelWidget(Widget): template = "genshi:sprox.widgets.tw2widgets.templates.modelLabel" controller = Param('controller', attribute=False, default=None) identifier = Param('identifier', attribute=False)
class JssorSlider(Widget): template = None resources = [jquery_js, jssor_slider_js] container = Param("id of the slider container") responsive = Param( "whether the slider should react to resizing etc.", default=False) options = Param( "(dict) of Jssor Slider options", default={}) _responsive_tmpl = """ function ScaleSlider() {{ var parentWidth = $('#{container}').parent().width(); if (parentWidth) {{ jssor_slider1.$ScaleWidth(parentWidth); }} else window.setTimeout(ScaleSlider, 30); }} ScaleSlider(); $(window).on('load', ScaleSlider); $(window).on('resize', ScaleSlider); $(window).on('orientationchange', ScaleSlider);""" _js_tmpl_begin = """jQuery(document).ready(function() {{ var options = {options}; var jssor_slider1 = new $JssorSlider$('{container}', options); """ _js_tmpl_end = "}});" _js_tmpl_nonresponsive = _js_tmpl_begin + _js_tmpl_end _js_tmpl_responsive = _js_tmpl_begin + _responsive_tmpl + _js_tmpl_end def _dict_to_js(self, options_dict): options_strs = [] for k, v in options_dict.items(): if isinstance(v, dict): v_str = self._dict_to_js(v) elif isinstance(v, (list, tuple)): v_str = "[{}]".format(", ".join(self._dict_to_js(x) for x in v)) elif isinstance(v, bool): v_str = "true" if v else "false" elif isinstance(v, str) and not (v.startswith("$") and v.endswith("$")): v_str = "'{}'".format(v) else: v_str = str(v) options_strs.append("'{k}':{v}".format(k=k, v=v_str)) return r"{{{options}}}".format(options=",".join(options_strs)) def prepare(self): super(JssorSlider, self).prepare() if self.responsive: js_tmpl = self._js_tmpl_responsive else: js_tmpl = self._js_tmpl_nonresponsive js_source = JSSource(src=js_tmpl.format( options=self._dict_to_js(self.options), container=self.container)) self.resources.append(js_source) def generate_output(self, displays_on): return u""
class ReCaptcha2Widget(Widget): resources = [recaptcha2_js] template = 'tw2.recaptcha2.templates.recaptcha2' sitekey = Param("Google reCAPTCHA API v2.0 site key", request_local=False) theme = Param("Theme to use (light, dark)", default=None) captcha_type = Param("Type of captcha to use (image, audio)", default=None) size = Param("Size of Captcha (normal, compact)", default=None) tabindex = Param("Optional tabindex of the widget", default=None) callback = Param( "Name of optional callback to execute when the user " "submits a successful CAPTCHA response", default=None) expired_callback = Param( "Name of optional callback to execute when " "the recaptcha response expires and the user " "needs to solve a new CAPTCHA", default=None) captcha_div_attrs = Variable() copyresponse_js = Variable() def prepare(self): if self.theme not in (None, 'light', 'dark'): raise ValueError("Theme must be 'light' or 'dark'") if self.captcha_type not in (None, 'image', 'audio'): raise ValueError("CAPTCHA type must be 'image' or 'audio'") if self.size is not None and not (isinstance(self.size, int) and self.size > 0): raise ValueError("Tabindex must be a positive integer") self.captcha_div_attrs = {'data-callback': 'recaptcha2_copy_response'} for attr in ('sitekey', 'theme', 'captcha_type', 'size', 'tabindex', 'expired_callback'): if getattr(self, attr): self.captcha_div_attrs['data-' + attr] = getattr(self, attr) if hasattr(self, 'compound_key'): input_name = self.compound_key else: input_name = self.compound_id if not self.callback: callback_js_source = "" else: callback_js_source = "{callback}(response);" self.copyresponse_js = JSSource( src="function recaptcha2_copy_response(response) {{" " var div = document.getElementById('{id}');" " var elements = document.getElementsByName('{name}');" " for (var i = 0; i < elements.length; i++) {{" " if (elements[i].type == 'hidden') {{" " elements[i].value = response;" " }}" " }}" " {callback_js_source}" "}}".format(id=self.compound_id, name=input_name, callback_js_source=callback_js_source)) super(ReCaptcha2Widget, self).prepare()
class FancyBox(Widget): resources = [ fancybox_css, jquery_js, mousewheel_js, easing_js, fancybox_js, static_resources ] fancybox_attrnames = ('padding', 'margin', 'opacity', 'modal', 'cyclic', 'scrolling', 'width', 'height', 'autoScale', 'autoDimensions', 'centerOnScroll', 'ajax', 'swf', 'hideOnOverlayClick', 'hideOnContentClick', 'overlayShow', 'overlayOpacity', 'overlayColor', 'titleShow', 'titlePosition', 'titleFromAlt', 'titleFormat', 'transitionIn', 'transitionOut', 'speedIn', 'speedOut', 'changeSpeed', 'changeFade', 'easingIn', 'easingOut', 'showCloseButton', 'showNavArrows', 'enableEscapeButton', 'onStart', 'onCancel', 'onComplete', 'onCleanup', 'onClosed') selector = Param("Selector specifying images to be decorated") padding = NDParam("Space between FancyBox wrapper and content") margin = NDParam("Space between viewport and FancyBox wrapper") opacity = NDParam("When True, transparency of content is changed " "for elastic transitions") modal = NDParam("When True, 'overlayShow' is set to True and " "'hideOnOverlayClick', 'hideOnContentClick', " "'enableEscapeButton', 'showCloseButton' " "are set to False") cyclic = NDParam("When True, galleries will be cyclic, allowing you to " "keep pressing next/back.") scrolling = NDParam("Set the overflow CSS property to create or hide " "scrollbars. Can be set to 'auto', 'yes', or 'no'") width = NDParam("Width for content types 'iframe' and 'swf'. Also set " "for inline content if 'autoDimensions' is set to False") height = NDParam("Height for content types 'iframe' and 'swf'. Also set " "for inline content if 'autoDimensions' is set to False") autoScale = NDParam("If True, FancyBox is scaled to fit in viewport") autoDimensions = NDParam("For inline and ajax views, resizes the view " "to the element received. Make sure it has " "dimensions otherwise this will give " "unexpected results") centerOnScroll = NDParam("When True, FancyBox is centered while " "scrolling page") ajax = NDParam("Ajax options. Note: 'error' and 'success' will be " "overwritten by FancyBox") swf = NDParam("Params to put on the swf object") hideOnOverlayClick = NDParam("Toggle if clicking the overlay should " "close FancyBox") hideOnContentClick = NDParam("Toggle if clicking the content should " "close FancyBox") overlayShow = NDParam("Toggle overlay") overlayOpacity = NDParam("Opacity of the overlay (from 0 to 1; " "default - 0.3)") overlayColor = NDParam("Color of the overlay") titleShow = NDParam("Toggle title") titlePosition = NDParam("The position of title. Can be set to " "'outside', 'inside' or 'over'") titleFromAlt = NDParam("Whether to use the alt property for the title.") titleFormat = NDParam("Callback to customize title area. You can set " "any html - custom image counter or even custom " "navigation") transitionIn = transitionOut = NDParam( "The transition type. Can be set to 'elastic', 'fade' or 'none'") speedIn = speedOut = NDParam( "Speed of the fade and elastic transitions, in milliseconds") changeSpeed = NDParam("Speed of resizing when changing gallery items, " "in milliseconds") changeFade = NDParam("Speed of the content fading while changing " "gallery items") easingIn = easingOut = NDParam("Easing used for elastic animations") showCloseButton = NDParam("Toggle close button") showNavArrows = NDParam("Toggle navigation arrows") enableEscapeButton = NDParam("Toggle if pressing Esc button closes " "FancyBox") onStart = NDParam("Will be called right before attempting to load " "the content") onCancel = NDParam("Will be called after loading is canceled") onComplete = NDParam("Will be called once the content is displayed") onCleanup = NDParam("Will be called just before closing") onClosed = NDParam("Will be called once FancyBox is closed") template = "" def prepare(self): super(FancyBox, self).prepare() if not self.selector: raise ValueError("FancyBox needs a selector") if (getattr(self, 'scrolling', None) and self.scrolling not in ('auto', 'yes', 'no')): raise ValueError("Scrolling must be 'auto', 'yes' or 'no'") if (getattr(self, 'overlayOpacity', None) and not (0.0 < float(self.overlayOpacity) < 1.0)): raise ValueError("Overlay opacity must be between 0.0 and 1.0") if (getattr(self, 'titlePosition', None) and self.titlePosition not in ('outside', 'inside', 'over')): raise ValueError("Title position must be 'outside', 'inside' " "or 'over'") if (getattr(self, 'transitionIn', None) and self.transitionIn not in ('elastic', 'fade', 'none')): raise ValueError("In transition must be 'elastic', 'fade' or " "'none'") if (getattr(self, "transitionOut", None) and self.transitionOut not in ('elastic', 'fade', 'none')): raise ValueError("Out transition must be 'elastic', 'fade' or " "'none'") params = {} for name in self.fancybox_attrnames: if getattr(self, name, None): params[name] = getattr(self, name) call = jQuery(self.selector).fancybox(params) self.add_call(call) def generate_output(self, displays_on): return u""
def NDParam(*p, **k): k.setdefault('default', None) return Param(*p, **k)
class EntityDefWidget(Widget): template = "genshi:sprox.widgets.tw2widgets.templates.entityDef" entity = Param('entity', attribute=False)
class SproxDataGrid(DataGrid): template = "sprox.widgets.tw2widgets.templates.datagrid" pks = Param('pks', attribute=False), xml_fields = Param('xml_fields', attribute=False, default=['actions']) value = []
class CDNLink(Link): external = Param( "(boolean) True if you would like to grab the file from a" " CDN instead of locally. Default: {!r}".format(_external_), default=_external_, request_local=False) cdn_handle = Param( "(string) handle of the CDN to pick if external == True" " and cdn_url is a dict mapping handles to URLs.", default=None, request_local=False) cdn_url = Param( "(dict(string: string) or string) URL of the resource on a" " CDN, or dict mapping CDN handles (see `cdn_handle`) to" " their URLs.", default=None, request_local=False) version = Param("(string) Specify the version of the resource to use.", default=None, request_local=False) variant = Param("(string) File variant, e.g. 'min' for minified. Default:" " {!r}".format(_variant_), default=_variant_, request_local=False) cdn_url = Param( "(dict(string: string) or string) URL of the resource on a CDN, " "or dict mapping CDN handles (see `external`) to their URLs.", request_local=False) def __init__(self, *p, **k): self._expanded_filename = None self._expanded_cdn_url = None self._link = None self._expansions = defaultdict(str) super(CDNLink, self).__init__(*p, **k) for param in ('version', 'variant'): value = getattr(self, param) if not value: continue self._expansions[param] = value for sep, sepname in ((".", "dot"), ("-", "dash"), ("_", "underscore"), ("/", "slash")): self._expansions[sepname + "_" + param] = sep + value self._expansions[param + "_" + sepname] = value + sep def prepare(self): if not self.external: modname = self.modname or self.__module__ twc.register_resource(modname, os.path.dirname(self.filename), whole_dir=self.whole_dir) super(CDNLink, self).prepare() def expand_string(self, string): return string.format(**self._expansions) @property def expanded_filename(self): if self._expanded_filename is None: self._expanded_filename = self.expand_string(self.filename) return self._expanded_filename def pick_cdn_url(self): assert len(self.cdn_url) if isinstance(self.cdn_url, six.text_type): return self.cdn_url # assume dict/mapping if self.external in self.cdn_url: return self.cdn_url[self.external] else: # use any CDN (unspecified which one) return next(iter(self.cdn_url.values())) @property def expanded_cdn_url(self): if self._expanded_cdn_url is None: self._expanded_cdn_url = self.expand_string(self.pick_cdn_url()) return self._expanded_cdn_url @property def link(self): if self._link is None: if self.external: self._link = self.expanded_cdn_url else: rl = twc.core.request_local() mw = rl['middleware'] self._link = "/" + "/".join( filter(None, (mw.config.script_name.strip("/"), mw.config.res_prefix.strip("/"), self.modname, self.expanded_filename))).replace("//", "/") return self._link
class RactiveWidget(Widget): template = '<div py:attrs="w.attrs"/>' inline_engine_name = 'kajiki' ractive_template = Param('The template of the Ractive Widget') ractive_init = Param( 'Ractive initialization function', default= 'function(options){ if (this._super !== undefined) this._super(options); }' ) ractive_base = Param('Name of the base Ractive class', default='Ractive') ractive_class = Param('Name of the Ractive Widget', default=None) ractive_params = Param( 'List of widget params that need to be passed to Ractive', default=[]) @classmethod def post_define(cls): if not hasattr(cls, 'ractive_template'): return if cls.ractive_class is None: cls.ractive_class = cls.__name__ template_id = '_'.join(('rt_template', cls.ractive_class)) cls.resources.append(RactiveGlobalJSSource()) cls.resources.append( RactiveTemplateSource(id=template_id, src=cls.ractive_template)) cls.resources.append( RactiveClassSource(id=cls.ractive_class, ractive_base=cls.ractive_base, template_id=template_id, src=cls.ractive_init)) @classmethod def activate(cls): resources = [r.req() for r in cls.resources] for r in resources: r.prepare() def prepare(self): super(RactiveWidget, self).prepare() self.safe_modify('attrs') self.attrs['id'] = self.id if self.value is None: self.value = {} ractive_params = dict([(prop, getattr(self, prop)) for prop in self.ractive_params]) try: self.value = self.value.copy() except: # Value must be a dictionary and so support copy self.value = {} self.value.update(ractive_params) self.safe_modify('resources') self.resources.append( JSSource(src=''' document.RAWidgets.display("%(id)s", %(RactiveClass)s, %(value)s); ''' % dict(id=self.id, RactiveClass=self.ractive_class, value=json_encode(self.value))))
class JssorBulletNavigator(Widget): template = 'genshi:tw2.jssor.slider.templates.bulletnavigator' opacity = Param("bullet opacity", default=0.7) cursor = Param("cursor type", default='pointer') normcolor = Param("color (normal)", default='gray') activecolor = Param("color (active)", default='#fff') mouseovercolor = Param("color (mouseover)", default='#d3d3d3') mousedowncolor = Param("color (mousedown)", default='#555555') bullet_width = Param("width of the bullet", default="12px") bullet_height = Param("height of the bullet", default="12px") border_width = Param("border width of the bullet", default="1px") horizontal_position = Param( "horizontal position (left, center, right)", default='center') horizontal_gap = Param("horizontal gap to border", default="16px") vertical_position = Param( "vertical position (top, center, bottom)", default='bottom') vertical_gap = Param("vertical gap to border", default="16px") spacing = Param("spacing between bullets", default="10px") _opacity_percent = Variable("computed opacity in percent") _horpos = Variable("computed style to set horizontal position") _vertpos = Variable("computed style to set vertical position") _center_style_tmpl = "{pos1}: 50%; margin-{pos2}: -50%; " + "; ".join( "{what}transform: translate{{XY}}(-50%)".format( what=what) for what in ( "", "-webkit-", "-ms-", "-o-", "-moz-")) _hor_center_style = _center_style_tmpl.format( pos1="left", pos2="right", XY="X") _vert_center_style = _center_style_tmpl.format( pos1="top", pos2="bottom", XY="Y") def prepare(self): super(JssorBulletNavigator, self).prepare() self._opacity_percent = int(self.opacity * 100) if self.horizontal_position == 'center': self._horpos = self._hor_center_style else: self._horpos = "{pos}: {gap}".format( pos=self.horizontal_position, gap=self.horizontal_gap) if self.vertical_position == 'center': self._vertpos = self._vert_center_style else: self._vertpos = "{pos}: {gap}".format( pos=self.vertical_position, gap=self.vertical_gap)
class ContainerWidget(DisplayOnlyWidget): template = "genshi:sprox.widgets.tw2widgets.templates.container" controller = Param('controller', attribute=False, default=None) css_class = "containerwidget" id_suffix = 'container'
class RactiveClassSource(UIDJSSource): template_id = Param('Ractive template dom id') ractive_base = Param('Ractive Base Widget Class', default='Ractive') inline_engine_name = 'kajiki' template = '''