PageRenderTime 62ms CodeModel.GetById 4ms RepoModel.GetById 0ms app.codeStats 0ms

/fa/jquery/renderers.py

https://bitbucket.org/gawel/fajquery
Python | 506 lines | 500 code | 4 blank | 2 comment | 0 complexity | 66a8a2cc1c901bb4bba07fad1cfdf3e1 MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.1
  1. # -*- coding: utf-8 -*-
  2. import os
  3. from simplejson import dumps
  4. from webhelpers.html import literal
  5. from webhelpers.html.tools import strip_tags
  6. from webhelpers import text
  7. from formalchemy import helpers as h
  8. from formalchemy import types
  9. from formalchemy import fields
  10. from formalchemy import config
  11. from postmarkup import render_bbcode
  12. from textile import textile as render_textile
  13. from markdown import markdown as render_markdown
  14. from utils import TemplateEngine
  15. from utils import templates
  16. from utils import load_datas
  17. from utils import url
  18. __doc__ = """
  19. This is the predefined renderers. You can have a look at the :doc:`../demo`.
  20. If you need your own, use the :class:`~fa.jquery.renderers.jQueryFieldRenderer`
  21. as base class.
  22. Some plugins use extra resources stored at the
  23. :class:`~fa.jquery.wsgi.StaticApp`. By default the prefix used is ``/jquery``.
  24. If you want to change this you can use this snippet:
  25. .. sourcecode:: python
  26. >>> from fa.jquery import renderers
  27. >>> renderers.url.root_url = '/jquery'
  28. """
  29. def alias(obj, **alias_kwargs):
  30. """decorator to make aliases with docs"""
  31. def wrapped(func):
  32. if hasattr(obj, 'func_name'):
  33. def wrapper(*args, **kwargs):
  34. """Alias for :func:`~fa.jquery.renderers.%s` with preset options %r""" % (obj.func_name, alias_kwargs)
  35. kwargs.update(alias_kwargs)
  36. return obj(*args, **kwargs)
  37. wrapper.func_name = func.func_name
  38. wrapper.func_doc = """Alias for :func:`~fa.jquery.renderers.%s` with preset options %r""" % (obj.func_name, alias_kwargs)
  39. return wrapper
  40. else:
  41. doc = """Alias for :class:`~fa.jquery.renderers.%s` with preset options %r""" % (obj.__name__, alias_kwargs)
  42. alias_kwargs['__doc__'] = doc
  43. return type(func.func_name, (obj,), alias_kwargs)
  44. return wrapped
  45. def jQueryFieldRenderer(plugin, show_input=False, tag='div', renderer=fields.TextFieldRenderer,
  46. resources_prefix=None, resources=[], **jq_options):
  47. """Extending jQuery.fa:
  48. .. sourcecode:: python
  49. >>> from testing import fs
  50. >>> renderer = jQueryFieldRenderer('myplugin', option1=True, option2=['a', 'b'])
  51. >>> field = fs.title.set(renderer=renderer)
  52. >>> print field.render() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  53. <div style="display:none;"><input autocomplete="off" id="Sample--title" name="Sample--title" type="text" /></div>
  54. <div id="Sample--title_myplugin"></div>
  55. <script type="text/javascript">
  56. jQuery.fa.myplugin('Sample--title', {"option2": ["a", "b"], "options": [], "option1": true});
  57. </script>...
  58. Then in your javascript code:
  59. .. sourcecode:: javascript
  60. jQuery.fa.extend({
  61. myplugin: function(field, plugin, options) {
  62. // do what you want
  63. }
  64. });
  65. Where field is the input, plugin the empty div and options the jq_options passed to the renderer.
  66. """
  67. template_name = jq_options.get('_template', 'jquery')
  68. template=templates.get_template('/renderers/%s.mako' % template_name)
  69. class Renderer(renderer):
  70. def render(self, **kwargs):
  71. if 'autocomplete' in kwargs:
  72. kwargs.pop('autocomplete')
  73. html = renderer.render(self, autocomplete='off', **kwargs)
  74. kwargs.update(self.jq_options)
  75. options = dict(
  76. tag=tag,
  77. html=html,
  78. plugin=plugin,
  79. name=self.name,
  80. show_input=show_input,
  81. resources=[url(r, prefix=self.resources_prefix) for r in resources],
  82. )
  83. try:
  84. options.update(options=dumps(kwargs))
  85. except TypeError:
  86. options.update(options={})
  87. try:
  88. return literal(self.template.render(**options))
  89. except:
  90. raise ValueError('Invalid options: %s' % options)
  91. return type('%sPluginRenderer' % plugin.title(), (Renderer,),
  92. dict(template=template,
  93. jq_options=jq_options,
  94. resources_prefix=resources_prefix))
  95. @alias(jQueryFieldRenderer)
  96. def plugin(): pass
  97. def AutoCompleteFieldRenderer(url_or_data, renderer=fields.TextFieldRenderer, **jq_options):
  98. """Use http://docs.jquery.com/UI/Autocomplete:
  99. .. sourcecode:: python
  100. >>> from testing import fs
  101. >>> field = fs.title.set(renderer=AutoCompleteFieldRenderer(['aa', 'bb']))
  102. With more advanced options:
  103. .. sourcecode:: python
  104. >>> field = fs.title.set(
  105. ... renderer=AutoCompleteFieldRenderer(
  106. ... '/my/uri',
  107. ... width=320,
  108. ... scroll=True,
  109. ... scrollHeight=300,
  110. ... ))
  111. """
  112. jq_options.update(source=url_or_data, show_input=False)
  113. return jQueryFieldRenderer('autocomplete', renderer=renderer, **jq_options)
  114. @alias(AutoCompleteFieldRenderer)
  115. def autocomplete(): pass
  116. def SortableTokenTextFieldRenderer(sep=';', show_input=False, **jq_options):
  117. """Sortable token using http://jqueryui.com/demos/sortable/:
  118. .. sourcecode:: python
  119. >>> from testing import fs
  120. >>> field = fs.sortable.set(renderer=SortableTokenTextFieldRenderer())
  121. >>> print field.render() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  122. <input type="hidden" value="fisrt;second" id="Sample--sortable" name="Sample--sortable" />
  123. <ul id="Sample--sortable_sortable" class="fa_sortable">
  124. <li class="ui-state-default" alt="fisrt"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>fisrt</li>
  125. <li class="ui-state-default" alt="second"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>second</li>
  126. </ul>
  127. <script type="text/javascript">
  128. jQuery.fa.sortable('Sample--sortable', {sep:';'});
  129. </script>...
  130. """
  131. class Renderer(fields.TextFieldRenderer):
  132. template=templates.get_template('/renderers/sortable.mako')
  133. def render_readonly(self):
  134. return ', '.join(self.raw_value.split(sep))
  135. def render(self, **kwargs):
  136. value=self.value.strip(sep)
  137. tokens = value and value.split(sep) or ''
  138. tokens = [(v, v) for v in tokens]
  139. kwargs.update(
  140. name=self.name,
  141. sep=sep,
  142. value=value,
  143. tokens=tokens,
  144. show_input=show_input,
  145. jq_options=dumps(jq_options),
  146. )
  147. return literal(self.template.render(**kwargs))
  148. return Renderer
  149. @alias(SortableTokenTextFieldRenderer)
  150. def sortable_token(): pass
  151. def ColorPickerFieldRenderer(colors=[], **jq_options):
  152. """Color Picker using http://www.syronex.com/software/jquery-color-picker:
  153. .. sourcecode:: python
  154. >>> from testing import fs
  155. >>> print fs.color.render() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  156. <div style="display:none;"><input autocomplete="off" id="Sample--color" name="Sample--color" type="text" /></div>
  157. <div id="Sample--color_colorpicker"></div>
  158. <script type="text/javascript">
  159. jQuery.fa.colorpicker('Sample--color', {"color": ["#FFFFFF", ...]});
  160. </script>
  161. <BLANKLINE>
  162. <div style="display:none;"><input autocomplete="off" id="Sample--color" name="Sample--color" type="text" /></div>
  163. <div id="Sample--color_colors"></div>
  164. <script type="text/javascript">
  165. jQuery.fa.colorpicker('Sample--color', {"color": ["#FFFFFF", ..., "#FF0096", "#B02B2C", "#000000"]});
  166. </script>...
  167. """
  168. jq_options['color'] = colors or [
  169. "#FFFFFF", "#EEEEEE", "#FFFF88", "#FF7400", "#CDEB8B", "#6BBA70",
  170. "#006E2E", "#C3D9FF", "#4096EE", "#356AA0", "#FF0096", "#B02B2C",
  171. "#000000"
  172. ]
  173. class Renderer(fields.TextFieldRenderer):
  174. def render_readonly(self, **kwargs):
  175. v = self.raw_value
  176. if not v:
  177. return ''
  178. return h.literal('<div style="background:%s;">%s</div>' % (v, v))
  179. return jQueryFieldRenderer('colorpicker', renderer=Renderer, **jq_options)
  180. @alias(ColorPickerFieldRenderer)
  181. def colorpicker(): pass
  182. class DateFieldRenderer(fields.DateFieldRenderer):
  183. """Use http://jqueryui.com/demos/datepicker/:
  184. .. sourcecode:: python
  185. >>> from testing import fs
  186. >>> print fs.date.render() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  187. <input type="text" autocomplete="off" size="10" value="" id="Sample--date" name="Sample--date" />
  188. <script type="text/javascript">
  189. jQuery.fa.datepicker('Sample--date', {"dateFormat": "yy-mm-dd"});
  190. </script>...
  191. """
  192. template = templates.get_template('/renderers/date.mako')
  193. jq_options = dict(dateFormat='yy-mm-dd')
  194. def render(self, **kwargs):
  195. value = self.value or ''
  196. value = value and value.split()[0] or ''
  197. kwargs.update(
  198. name=self.name,
  199. value=value,
  200. jq_options=dumps(self.jq_options),
  201. )
  202. return literal(self.template.render(**kwargs))
  203. def _serialized_value(self):
  204. value = self.params.getone(self.name) or ''
  205. return value
  206. @alias(DateFieldRenderer)
  207. def date(): pass
  208. class DateTimeFieldRenderer(DateFieldRenderer, fields.TimeFieldRenderer):
  209. """Use http://jqueryui.com/demos/datepicker/"""
  210. format = '%Y-%m-%d %H:%M:%S'
  211. template = templates.get_template('/renderers/date.mako')
  212. jq_options = dict(dateFormat='yy-mm-dd')
  213. def render(self, **kwargs):
  214. return h.content_tag('span', DateFieldRenderer.render(self, **kwargs) + literal(' ') + \
  215. literal(fields.TimeFieldRenderer._render(self, **kwargs)))
  216. def _serialized_value(self):
  217. date = DateFieldRenderer._serialized_value(self)
  218. if date:
  219. return date + ' ' + fields.TimeFieldRenderer._serialized_value(self)
  220. else:
  221. return ''
  222. @alias(DateTimeFieldRenderer)
  223. def datetime(): pass
  224. def SliderFieldRenderer(min=0, max=100, show_value=True, **jq_options):
  225. """Fill an integer field using http://jqueryui.com/demos/slider/:
  226. .. sourcecode:: python
  227. >>> from testing import fs
  228. >>> field = fs.slider.set(renderer=SliderFieldRenderer(min=10, max=150))
  229. """
  230. jq_options.update(min=min, max=max, show_value=show_value)
  231. return jQueryFieldRenderer('slider', renderer=fields.IntegerFieldRenderer, **jq_options)
  232. @alias(SliderFieldRenderer)
  233. def slider(): pass
  234. def SelectableFieldRenderer(multiple=False, **jq_options):
  235. """Fill a list field using http://jqueryui.com/demos/selectable/:
  236. .. sourcecode:: python
  237. >>> from testing import fs
  238. >>> print fs.selectable.render() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  239. <div style="display:none;"><select autocomplete="off" id="Sample--selectable" name="Sample--selectable">
  240. <option value="a">a</option>
  241. <option value="b">b</option>
  242. <option value="c">c</option>
  243. <option value="d">d</option>
  244. <option value="e">e</option>
  245. <option value="f">f</option>
  246. </select></div>
  247. <div id="Sample--selectable_selectable"></div>
  248. <script type="text/javascript">
  249. jQuery.fa.selectable('Sample--selectable', {"multiple": false, "options": ["a", "b", "c", "d", "e", "f"]});
  250. </script>
  251. """
  252. jq_options.update(multiple=multiple)
  253. class Renderer(fields.SelectFieldRenderer):
  254. def render(self, *args, **kwargs):
  255. kwargs['multiple'] = self.multiple
  256. return fields.SelectFieldRenderer.render(self, *args, **kwargs)
  257. return jQueryFieldRenderer('selectable',
  258. renderer=type('SelectableFieldRenderer', (Renderer,), dict(multiple=multiple)),
  259. **jq_options)
  260. @alias(SelectableFieldRenderer, multiple=False)
  261. def selectable():pass
  262. @alias(SelectableFieldRenderer, multiple=True)
  263. def selectables():pass
  264. def ButtonSetFieldRenderer(multiple=False, **jq_options):
  265. """Fill a list field using http://jqueryui.com/demos/button/:
  266. .. sourcecode:: python
  267. >>> from testing import fs
  268. >>> print fs.radioset.render() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  269. <div style="display:none;"><input autocomplete="off" id="Sample--radioset_0"
  270. name="Sample--radioset" type="radio"
  271. value="a" /><label for="Sample--radioset_0">a</label>...</div>
  272. <div id="Sample--radioset_buttonset"></div>
  273. <script type="text/javascript">
  274. jQuery.fa.buttonset('Sample--radioset', {"multiple": false, "options": ["a", "b", "c", "d", "e", "f"]});
  275. </script>
  276. <BLANKLINE>
  277. """
  278. jq_options.update(multiple=multiple)
  279. Renderer = multiple and fields.CheckBoxSet or fields.RadioSet
  280. return jQueryFieldRenderer('buttonset', renderer=Renderer, **jq_options)
  281. @alias(ButtonSetFieldRenderer, multiple=False)
  282. def radioset():pass
  283. @alias(ButtonSetFieldRenderer, multiple=True)
  284. def checkboxset():pass
  285. def RichTextFieldRenderer(use='tinymce', resources_prefix=None, **jq_options):
  286. """RichTextFieldRenderer using TinyMCE or MarkitUp!:
  287. .. sourcecode:: python
  288. >>> from testing import fs
  289. >>> field = fs.rich.set(renderer=RichTextFieldRenderer(use='tinymce', theme='advanced'))
  290. >>> print field.render() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  291. <script type="text/javascript">
  292. jQuery.fa.add_resource("/jquery/tiny_mce/tiny_mce.js");
  293. jQuery.fa.add_resource("/jquery/tiny_mce/jquery.tinymce.js");
  294. </script>
  295. <textarea autocomplete="off" id="Sample--rich" name="Sample--rich"></textarea>
  296. <div id="Sample--rich_tinymce"></div>
  297. <script type="text/javascript">
  298. jQuery.fa.tinymce('Sample--rich', {... "theme": "advanced", ...});
  299. </script>
  300. If you want to use your own TinyMCE/MarkitUp! version:
  301. .. sourcecode:: python
  302. >>> field = fs.rich.set(renderer=RichTextFieldRenderer(use='tinymce', resources_prefix='/my_js'))
  303. >>> print field.render() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  304. <script type="text/javascript">
  305. jQuery.fa.add_resource("/my_js/tiny_mce/tiny_mce.js");
  306. jQuery.fa.add_resource("/my_js/tiny_mce/jquery.tinymce.js");
  307. </script>
  308. ...
  309. There is also some aliases:
  310. .. sourcecode:: python
  311. >>> field = fs.rich.set(renderer=tinymce())
  312. >>> print field.render() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  313. <script type="text/javascript">
  314. jQuery.fa.add_resource("/jquery/tiny_mce/tiny_mce.js");
  315. jQuery.fa.add_resource("/jquery/tiny_mce/jquery.tinymce.js");
  316. </script>
  317. <textarea autocomplete="off" id="Sample--rich" name="Sample--rich"></textarea>
  318. ...
  319. >>> field = fs.rich.set(renderer=textile())
  320. >>> print field.render() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  321. <script type="text/javascript">
  322. jQuery.fa.add_resource("/jquery/markitup/jquery.markitup.pack.js");
  323. jQuery.fa.add_resource("/jquery/markitup/skins/simple/style.css");
  324. jQuery.fa.add_resource("/jquery/markitup/sets/textile/style.css");
  325. jQuery.fa.add_resource("/jquery/markitup/sets/textile/set.js");
  326. </script>
  327. <textarea autocomplete="off" id="Sample--rich" name="Sample--rich"></textarea>
  328. ...
  329. >>> field = fs.rich.set(renderer=markdown())
  330. >>> print field.render() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  331. <script type="text/javascript">
  332. jQuery.fa.add_resource("/jquery/markitup/jquery.markitup.pack.js");
  333. jQuery.fa.add_resource("/jquery/markitup/skins/simple/style.css");
  334. jQuery.fa.add_resource("/jquery/markitup/sets/markdown/style.css");
  335. jQuery.fa.add_resource("/jquery/markitup/sets/markdown/set.js");
  336. </script>
  337. <textarea autocomplete="off" id="Sample--rich" name="Sample--rich"></textarea>
  338. ...
  339. >>> field = fs.rich.set(renderer=bbcode())
  340. >>> print field.render() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  341. <script type="text/javascript">
  342. jQuery.fa.add_resource("/jquery/markitup/jquery.markitup.pack.js");
  343. jQuery.fa.add_resource("/jquery/markitup/skins/simple/style.css");
  344. jQuery.fa.add_resource("/jquery/markitup/sets/bbcode/style.css");
  345. jQuery.fa.add_resource("/jquery/markitup/sets/bbcode/set.js");
  346. </script>
  347. <textarea autocomplete="off" id="Sample--rich" name="Sample--rich"></textarea>
  348. ...
  349. """
  350. plugin_name = use
  351. defaults = {}
  352. if use == 'tinymce':
  353. resources = ['tiny_mce/tiny_mce.js', 'tiny_mce/jquery.tinymce.js']
  354. defaults['theme'] = 'advanced'
  355. defaults['theme_advanced_toolbar_location'] = "top"
  356. defaults['theme_advanced_toolbar_align'] = "left"
  357. defaults['theme_advanced_statusbar_location'] = "bottom"
  358. defaults['theme_advanced_resizing'] = True
  359. if 'content_css' in jq_options:
  360. jq_options['content_css'] = [url(c, prefix=resources_prefix) for c in jq_options['content_css']]
  361. elif use in ('textile', 'bbcode', 'markdown'):
  362. plugin_name = 'markitup'
  363. defaults['nameSpace'] = use
  364. defaults['resizeHandle'] = True
  365. defaults['previewAutoRefresh'] = True
  366. defaults['previewParserPath'] = url('markup_parser.html?markup=%s' % use)
  367. resources = ['markitup/jquery.markitup.pack.js',
  368. 'markitup/skins/simple/style.css',
  369. 'markitup/sets/%s/style.css' % use,
  370. 'markitup/sets/%s/set.js' % use]
  371. else:
  372. resources = []
  373. for k, v in defaults.items():
  374. if k not in jq_options:
  375. jq_options[k] = v
  376. class Renderer(fields.TextAreaFieldRenderer):
  377. markup = use
  378. def render_textile(self, **kwargs):
  379. value = self.raw_value
  380. return value and render_textile(value) or ''
  381. def render_bbcode(self, **kwargs):
  382. value = self.raw_value
  383. return value and render_bbcode(value) or ''
  384. def render_markdown(self, **kwargs):
  385. value = self.raw_value
  386. return value and render_markdown(value) or ''
  387. def render_readonly(self, **kwargs):
  388. meth = getattr(self, 'render_%s' % self.markup, None)
  389. if meth is not None:
  390. return meth()
  391. return fields.TextAreaFieldRenderer.render_readonly(self, **kwargs)
  392. return jQueryFieldRenderer(plugin_name, show_input=True, renderer=Renderer,
  393. resources_prefix=resources_prefix, resources=resources, **jq_options)
  394. @alias(RichTextFieldRenderer, use='tinymce')
  395. def tinymce(): pass
  396. @alias(RichTextFieldRenderer, use='textile')
  397. def textile(): pass
  398. @alias(RichTextFieldRenderer, use='markdown')
  399. def markdown(): pass
  400. @alias(RichTextFieldRenderer, use='bbcode')
  401. def bbcode(): pass
  402. def ellipsys(renderer):
  403. """Update a renderer to remove tags and strip text"""
  404. renderer = type(renderer)
  405. class EllipsysFieldRenderer(renderer):
  406. def render_readonly(self, **kwargs):
  407. value = super(EllipsysFieldRenderer, self).render_readonly(**kwargs)
  408. value = text.truncate(strip_tags(value), 30) if value else ''
  409. return value
  410. return EllipsysFieldRenderer
  411. default_renderers = {
  412. types.Date:date,
  413. types.DateTime:datetime,
  414. types.HTML: markdown(),
  415. types.Slider: slider,
  416. types.Color: ColorPickerFieldRenderer,
  417. types.Selectable: selectable(),
  418. types.Selectables: selectables(),
  419. }