1 """widget classes for form construction |
1 # organization: Logilab |
2 |
2 # copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. |
3 :organization: Logilab |
3 # contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
4 :copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. |
4 # license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
5 """ |
6 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
6 Widgets |
|
7 ~~~~~~~ |
|
8 |
|
9 .. Note:: |
|
10 A widget is responsible for the display of a field. It may use more than one |
|
11 HTML input tags. When the form is posted, a widget is also reponsible to give |
|
12 back to the field something it can understand. |
|
13 |
|
14 Of course you can not use any widget with any field... |
|
15 |
|
16 .. autoclass:: cubicweb.web.formwidgets.FieldWidget |
|
17 |
|
18 HTML <input> based widgets |
|
19 '''''''''''''''''''''''''' |
|
20 |
|
21 .. autoclass:: cubicweb.web.formwidgets.HiddenInput |
|
22 .. autoclass:: cubicweb.web.formwidgets.TextInput |
|
23 .. autoclass:: cubicweb.web.formwidgets.PasswordSingleInput |
|
24 .. autoclass:: cubicweb.web.formwidgets.FileInput |
|
25 .. autoclass:: cubicweb.web.formwidgets.ButtonInput |
|
26 |
|
27 Other standard HTML widgets |
|
28 ''''''''''''''''''''''''''' |
|
29 |
|
30 .. autoclass:: cubicweb.web.formwidgets.TextArea |
|
31 .. autoclass:: cubicweb.web.formwidgets.Select |
|
32 .. autoclass:: cubicweb.web.formwidgets.CheckBox |
|
33 .. autoclass:: cubicweb.web.formwidgets.Radio |
|
34 |
|
35 Date and time widgets |
|
36 ''''''''''''''''''''' |
|
37 |
|
38 .. autoclass:: cubicweb.web.formwidgets.DateTimePicker |
|
39 .. autoclass:: cubicweb.web.formwidgets.JQueryDateTimePicker |
|
40 .. autoclass:: cubicweb.web.formwidgets.JQueryDatePicker |
|
41 .. autoclass:: cubicweb.web.formwidgets.JQueryTimePicker |
|
42 |
|
43 Ajax / javascript widgets |
|
44 ''''''''''''''''''''''''' |
|
45 |
|
46 .. autoclass:: cubicweb.web.formwidgets.FCKEditor |
|
47 .. autoclass:: cubicweb.web.formwidgets.AjaxWidget |
|
48 .. autoclass:: cubicweb.web.formwidgets.AutoCompletionWidget |
|
49 |
|
50 .. kill or document AddComboBoxWidget |
|
51 .. kill or document StaticFileAutoCompletionWidget |
|
52 .. kill or document LazyRestrictedAutoCompletionWidget |
|
53 .. kill or document RestrictedAutoCompletionWidget |
|
54 |
|
55 Other widgets |
|
56 ''''''''''''' |
|
57 .. autoclass:: cubicweb.web.formwidgets.PasswordInput |
|
58 .. autoclass:: cubicweb.web.formwidgets.IntervalWidget |
|
59 .. autoclass:: cubicweb.web.formwidgets.HorizontalLayoutWidget |
|
60 .. autoclass:: cubicweb.web.formwidgets.EditableURLWidget |
|
61 |
|
62 Form controls |
|
63 ''''''''''''' |
|
64 Those classes are not proper widget (they are not associated to |
|
65 field) but are used as form controls. Their API is similar |
|
66 to widgets except that `field` argument given to :meth:`render` |
|
67 will be `None`. |
|
68 |
|
69 .. autoclass:: cubicweb.web.formwidgets.Button |
|
70 .. autoclass:: cubicweb.web.formwidgets.SubmitButton |
|
71 .. autoclass:: cubicweb.web.formwidgets.ResetButton |
|
72 .. autoclass:: cubicweb.web.formwidgets.ImgButton |
7 """ |
73 """ |
8 __docformat__ = "restructuredtext en" |
74 __docformat__ = "restructuredtext en" |
9 |
75 |
10 from datetime import date |
76 from datetime import date |
11 from warnings import warn |
77 from warnings import warn |
17 from cubicweb import tags, uilib |
83 from cubicweb import tags, uilib |
18 from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError |
84 from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError |
19 |
85 |
20 |
86 |
21 class FieldWidget(object): |
87 class FieldWidget(object): |
22 """abstract widget class""" |
88 """The abstract base class for widgets. |
23 # javascript / css files required by the widget |
89 |
|
90 **Attributes** |
|
91 |
|
92 Here are standard attributes of a widget, that may be set on concret |
|
93 class to override default behaviours: |
|
94 |
|
95 :attr:`needs_js` |
|
96 list of javascript files needed by the widget. |
|
97 :attr:`needs_css` |
|
98 list of css files needed by the widget. |
|
99 :attr:`setdomid` |
|
100 flag telling if HTML DOM identifier should be set on input. |
|
101 :attr:`settabindex` |
|
102 flag telling if HTML tabindex attribute of inputs should be set. |
|
103 :attr:`suffix` |
|
104 string to use a suffix when generating input, to ease usage as a |
|
105 sub-widgets (eg widget used by another widget) |
|
106 :attr:`vocabulary_widget` |
|
107 flag telling if this widget expect a vocabulary |
|
108 |
|
109 Also, widget instances takes as first argument a `attrs` dictionary which |
|
110 will be stored in the attribute of the same name. It contains HTML |
|
111 attributes that should be set in the widget's input tag (though concret |
|
112 classes may ignore it). |
|
113 |
|
114 .. currentmodule:: cubicweb.web.formwidgets |
|
115 |
|
116 **Form generation methods** |
|
117 |
|
118 .. automethod:: render |
|
119 .. automethod:: _render |
|
120 .. automethod:: values |
|
121 .. automethod:: attributes |
|
122 |
|
123 **Post handling methods** |
|
124 |
|
125 .. automethod:: process_field_data |
|
126 |
|
127 """ |
24 needs_js = () |
128 needs_js = () |
25 needs_css = () |
129 needs_css = () |
26 # automatically set id and tabindex attributes ? |
|
27 setdomid = True |
130 setdomid = True |
28 settabindex = True |
131 settabindex = True |
29 # to ease usage as a sub-widgets (eg widget used by another widget) |
|
30 suffix = None |
132 suffix = None |
31 # does this widget expect a vocabulary |
133 # does this widget expect a vocabulary |
32 vocabulary_widget = False |
134 vocabulary_widget = False |
33 |
135 |
34 def __init__(self, attrs=None, setdomid=None, settabindex=None, suffix=None): |
136 def __init__(self, attrs=None, setdomid=None, settabindex=None, suffix=None): |
49 if self.needs_js: |
151 if self.needs_js: |
50 form._cw.add_js(self.needs_js) |
152 form._cw.add_js(self.needs_js) |
51 if self.needs_css: |
153 if self.needs_css: |
52 form._cw.add_css(self.needs_css) |
154 form._cw.add_css(self.needs_css) |
53 |
155 |
54 |
|
55 def render(self, form, field, renderer=None): |
156 def render(self, form, field, renderer=None): |
|
157 """Called to render the widget for the given `field` in the given |
|
158 `form`. Return a unicode string containing the HTML snippet. |
|
159 |
|
160 You will usually prefer to override the :meth:`_render` method so you |
|
161 don't have to handle addition of needed javascript / css files. |
|
162 """ |
56 self.add_media(form) |
163 self.add_media(form) |
57 return self._render(form, field, renderer) |
164 return self._render(form, field, renderer) |
58 |
165 |
59 def _render(self, form, field, renderer): |
166 def _render(self, form, field, renderer): |
|
167 """This is the method you have to implement in concret widget classes. |
|
168 """ |
60 raise NotImplementedError() |
169 raise NotImplementedError() |
61 |
170 |
62 def format_value(self, form, field, value): |
171 def format_value(self, form, field, value): |
63 return field.format_value(form._cw, value) |
172 return field.format_value(form._cw, value) |
64 |
173 |
73 if self.settabindex and not 'tabindex' in attrs: |
182 if self.settabindex and not 'tabindex' in attrs: |
74 attrs['tabindex'] = form._cw.next_tabindex() |
183 attrs['tabindex'] = form._cw.next_tabindex() |
75 return attrs |
184 return attrs |
76 |
185 |
77 def values(self, form, field): |
186 def values(self, form, field): |
|
187 """Return the current *string* values (i.e. for display in an HTML |
|
188 string) for the given field. This method returns a list of values since |
|
189 it's suitable for all kind of widgets, some of them taking multiple |
|
190 values, but you'll get a single value in the list in most cases. |
|
191 |
|
192 Those values are searched in: |
|
193 |
|
194 1. previously submitted form values if any (on validation error) |
|
195 |
|
196 2. req.form (specified using request parameters) |
|
197 |
|
198 3. extra form values given to form.render call (specified the code |
|
199 generating the form) |
|
200 |
|
201 4. field's typed value (returned by its |
|
202 :meth:`~cubicweb.web.formfields.Field.typed_value` method) |
|
203 |
|
204 Values found in 1. and 2. are expected te be already some 'display |
|
205 value' (eg a string) while those found in 3. and 4. are expected to be |
|
206 correctly typed value. |
|
207 |
|
208 3 and 4 are handle by the :meth:`typed_value` method to ease reuse in |
|
209 concret classes. |
|
210 """ |
78 values = None |
211 values = None |
79 if not field.ignore_req_params: |
212 if not field.ignore_req_params: |
80 qname = field.input_name(form, self.suffix) |
213 qname = field.input_name(form, self.suffix) |
81 # value from a previous post that has raised a validation error |
214 # value from a previous post that has raised a validation error |
82 if qname in form.form_previous_values: |
215 if qname in form.form_previous_values: |
110 if field.name != qname and field.name in form.formvalues: |
243 if field.name != qname and field.name in form.formvalues: |
111 return form.formvalues[field.name] |
244 return form.formvalues[field.name] |
112 return field.typed_value(form) |
245 return field.typed_value(form) |
113 |
246 |
114 def process_field_data(self, form, field): |
247 def process_field_data(self, form, field): |
|
248 """Return process posted value(s) for widget and return something |
|
249 understandable by the associated `field`. That value may be correctly |
|
250 typed or a string that the field may parse. |
|
251 """ |
115 posted = form._cw.form |
252 posted = form._cw.form |
116 val = posted.get(field.input_name(form, self.suffix)) |
253 val = posted.get(field.input_name(form, self.suffix)) |
117 if isinstance(val, basestring): |
254 if isinstance(val, basestring): |
118 val = val.strip() |
255 val = val.strip() |
119 return val |
256 return val |
150 |
287 |
151 |
288 |
152 # basic html widgets ########################################################### |
289 # basic html widgets ########################################################### |
153 |
290 |
154 class TextInput(Input): |
291 class TextInput(Input): |
155 """<input type='text'>""" |
292 """Simple <input type='text'>, will return an unicode string.""" |
156 type = 'text' |
293 type = 'text' |
157 |
294 |
158 |
295 |
|
296 class PasswordSingleInput(Input): |
|
297 """Simple <input type='password'>, will return an utf-8 encoded string. |
|
298 |
|
299 You may prefer using the :class:`~cubicweb.web.formwidgets.PasswordInput` |
|
300 widget which handles password confirmation. |
|
301 """ |
|
302 type = 'password' |
|
303 |
|
304 def process_field_data(self, form, field): |
|
305 value = super(PasswordSingleInput, self).process_field_data(form, field) |
|
306 if value is not None: |
|
307 return value.encode('utf-8') |
|
308 return value |
|
309 |
|
310 |
159 class PasswordInput(Input): |
311 class PasswordInput(Input): |
160 """<input type='password'> and its confirmation field (using |
312 """<input type='password'> and a confirmation input. Form processing will |
161 <field's name>-confirm as name) |
313 fail if password and confirmation differs, else it will return the password |
|
314 as an utf-8 encoded string. |
162 """ |
315 """ |
163 type = 'password' |
316 type = 'password' |
164 |
317 |
165 def _render(self, form, field, renderer): |
318 def _render(self, form, field, renderer): |
166 assert self.suffix is None, 'suffix not supported' |
319 assert self.suffix is None, 'suffix not supported' |
184 return None |
337 return None |
185 return passwd1.encode('utf-8') |
338 return passwd1.encode('utf-8') |
186 raise ProcessFormError(form._cw._("password and confirmation don't match")) |
339 raise ProcessFormError(form._cw._("password and confirmation don't match")) |
187 |
340 |
188 |
341 |
189 class PasswordSingleInput(Input): |
|
190 """<input type='password'> without a confirmation field""" |
|
191 type = 'password' |
|
192 |
|
193 def process_field_data(self, form, field): |
|
194 value = super(PasswordSingleInput, self).process_field_data(form, field) |
|
195 if value is not None: |
|
196 return value.encode('utf-8') |
|
197 return value |
|
198 |
|
199 |
|
200 class FileInput(Input): |
342 class FileInput(Input): |
201 """<input type='file'>""" |
343 """Simple <input type='file'>, will return a tuple (name, stream) where |
|
344 name is the posted file name and stream a file like object containing the |
|
345 posted file data. |
|
346 """ |
202 type = 'file' |
347 type = 'file' |
203 |
348 |
204 def values(self, form, field): |
349 def values(self, form, field): |
205 # ignore value which makes no sense here (XXX even on form validation error?) |
350 # ignore value which makes no sense here (XXX even on form validation error?) |
206 return ('',) |
351 return ('',) |
207 |
352 |
208 |
353 |
209 class HiddenInput(Input): |
354 class HiddenInput(Input): |
210 """<input type='hidden'>""" |
355 """Simple <input type='hidden'> for hidden value, will return an unicode |
|
356 string. |
|
357 """ |
211 type = 'hidden' |
358 type = 'hidden' |
212 setdomid = False # by default, don't set id attribute on hidden input |
359 setdomid = False # by default, don't set id attribute on hidden input |
213 settabindex = False |
360 settabindex = False |
214 |
361 |
215 |
362 |
216 class ButtonInput(Input): |
363 class ButtonInput(Input): |
217 """<input type='button'> |
364 """Simple <input type='button'>, will return an unicode string. |
218 |
365 |
219 if you want a global form button, look at the Button, SubmitButton, |
366 If you want a global form button, look at the :class:`Button`, |
220 ResetButton and ImgButton classes below. |
367 :class:`SubmitButton`, :class:`ResetButton` and :class:`ImgButton` below. |
221 """ |
368 """ |
222 type = 'button' |
369 type = 'button' |
223 |
370 |
224 |
371 |
225 class TextArea(FieldWidget): |
372 class TextArea(FieldWidget): |
226 """<textarea>""" |
373 """Simple <textarea>, will return an unicode string.""" |
227 |
374 |
228 def _render(self, form, field, renderer): |
375 def _render(self, form, field, renderer): |
229 values, attrs = self.values_and_attributes(form, field) |
376 values, attrs = self.values_and_attributes(form, field) |
230 attrs.setdefault('onkeyup', 'autogrow(this)') |
377 attrs.setdefault('onkeyup', 'autogrow(this)') |
231 if not values: |
378 if not values: |
243 return tags.textarea(value, name=field.input_name(form, self.suffix), |
390 return tags.textarea(value, name=field.input_name(form, self.suffix), |
244 **attrs) |
391 **attrs) |
245 |
392 |
246 |
393 |
247 class FCKEditor(TextArea): |
394 class FCKEditor(TextArea): |
248 """FCKEditor enabled <textarea>""" |
395 """FCKEditor enabled <textarea>, will return an unicode string containing |
|
396 HTML formated text. |
|
397 """ |
249 def __init__(self, *args, **kwargs): |
398 def __init__(self, *args, **kwargs): |
250 super(FCKEditor, self).__init__(*args, **kwargs) |
399 super(FCKEditor, self).__init__(*args, **kwargs) |
251 self.attrs['cubicweb:type'] = 'wysiwyg' |
400 self.attrs['cubicweb:type'] = 'wysiwyg' |
252 |
401 |
253 def _render(self, form, field, renderer): |
402 def _render(self, form, field, renderer): |
254 form._cw.fckeditor_config() |
403 form._cw.fckeditor_config() |
255 return super(FCKEditor, self)._render(form, field, renderer) |
404 return super(FCKEditor, self)._render(form, field, renderer) |
256 |
405 |
257 |
406 |
258 class Select(FieldWidget): |
407 class Select(FieldWidget): |
259 """<select>, for field having a specific vocabulary""" |
408 """Simple <select>, for field having a specific vocabulary. Will return |
|
409 an unicode string, or a list of unicode strings. |
|
410 """ |
260 vocabulary_widget = True |
411 vocabulary_widget = True |
261 |
412 |
262 def __init__(self, attrs=None, multiple=False, **kwargs): |
413 def __init__(self, attrs=None, multiple=False, **kwargs): |
263 super(Select, self).__init__(attrs, **kwargs) |
414 super(Select, self).__init__(attrs, **kwargs) |
264 self._multiple = multiple |
415 self._multiple = multiple |
292 return tags.select(name=field.input_name(form, self.suffix), |
443 return tags.select(name=field.input_name(form, self.suffix), |
293 multiple=self._multiple, options=options, **attrs) |
444 multiple=self._multiple, options=options, **attrs) |
294 |
445 |
295 |
446 |
296 class CheckBox(Input): |
447 class CheckBox(Input): |
297 """<input type='checkbox'>, for field having a specific vocabulary. One |
448 """Simple <input type='checkbox'>, for field having a specific |
298 input will be generated for each possible value. |
449 vocabulary. One input will be generated for each possible value. |
|
450 |
|
451 You can specify separator using the `separator` constructor argument, by |
|
452 default <br/> is used. |
299 """ |
453 """ |
300 type = 'checkbox' |
454 type = 'checkbox' |
301 vocabulary_widget = True |
455 vocabulary_widget = True |
302 |
456 |
303 def __init__(self, attrs=None, separator=u'<br/>\n', **kwargs): |
457 def __init__(self, attrs=None, separator=u'<br/>\n', **kwargs): |
332 options.append(u'%s %s' % (tag, label)) |
486 options.append(u'%s %s' % (tag, label)) |
333 return sep.join(options) |
487 return sep.join(options) |
334 |
488 |
335 |
489 |
336 class Radio(CheckBox): |
490 class Radio(CheckBox): |
337 """<input type='radio'>, for field having a specific vocabulary. One |
491 """Simle <input type='radio'>, for field having a specific vocabulary. One |
338 input will be generated for each possible value. |
492 input will be generated for each possible value. |
|
493 |
|
494 You can specify separator using the `separator` constructor argument, by |
|
495 default <br/> is used. |
339 """ |
496 """ |
340 type = 'radio' |
497 type = 'radio' |
341 |
498 |
342 |
499 |
343 # javascript widgets ########################################################### |
500 # javascript widgets ########################################################### |
344 |
501 |
345 class DateTimePicker(TextInput): |
502 class DateTimePicker(TextInput): |
346 """<input type='text' + javascript date/time picker for date or datetime |
503 """<input type='text'> + javascript date/time picker for date or datetime |
347 fields |
504 fields. Will return the date or datetime as an unicode string. |
348 """ |
505 """ |
349 monthnames = ('january', 'february', 'march', 'april', |
506 monthnames = ('january', 'february', 'march', 'april', |
350 'may', 'june', 'july', 'august', |
507 'may', 'june', 'july', 'august', |
351 'september', 'october', 'november', 'december') |
508 'september', 'october', 'november', 'december') |
352 daynames = ('monday', 'tuesday', 'wednesday', 'thursday', |
509 daynames = ('monday', 'tuesday', 'wednesday', 'thursday', |
384 form._cw.external_resource('CALENDAR_ICON'), |
541 form._cw.external_resource('CALENDAR_ICON'), |
385 form._cw._('calendar'), helperid) ) |
542 form._cw._('calendar'), helperid) ) |
386 |
543 |
387 |
544 |
388 class JQueryDatePicker(FieldWidget): |
545 class JQueryDatePicker(FieldWidget): |
389 """use jquery.ui.datepicker to define a date time picker""" |
546 """Use jquery.ui.datepicker to define a date picker. Will return the date as |
|
547 an unicode string. |
|
548 """ |
390 needs_js = ('jquery.ui.js', ) |
549 needs_js = ('jquery.ui.js', ) |
391 needs_css = ('jquery.ui.css',) |
550 needs_css = ('jquery.ui.css',) |
392 |
551 |
393 def __init__(self, datestr=None, **kwargs): |
552 def __init__(self, datestr=None, **kwargs): |
394 super(JQueryDatePicker, self).__init__(**kwargs) |
553 super(JQueryDatePicker, self).__init__(**kwargs) |
411 return tags.input(id=domid, name=domid, value=value, |
570 return tags.input(id=domid, name=domid, value=value, |
412 type='text', size='10') |
571 type='text', size='10') |
413 |
572 |
414 |
573 |
415 class JQueryTimePicker(FieldWidget): |
574 class JQueryTimePicker(FieldWidget): |
416 """use jquery.timePicker.js to define a js time picker""" |
575 """Use jquery.timePicker to define a time picker. Will return the time as an |
|
576 unicode string. |
|
577 """ |
417 needs_js = ('jquery.timePicker.js',) |
578 needs_js = ('jquery.timePicker.js',) |
418 needs_css = ('jquery.timepicker.css',) |
579 needs_css = ('jquery.timepicker.css',) |
419 |
580 |
420 def __init__(self, timestr=None, timesteps=30, separator=u':', **kwargs): |
581 def __init__(self, timestr=None, timesteps=30, separator=u':', **kwargs): |
421 super(JQueryTimePicker, self).__init__(**kwargs) |
582 super(JQueryTimePicker, self).__init__(**kwargs) |
435 return tags.input(id=domid, name=domid, value=value, |
596 return tags.input(id=domid, name=domid, value=value, |
436 type='text', size='5') |
597 type='text', size='5') |
437 |
598 |
438 |
599 |
439 class JQueryDateTimePicker(FieldWidget): |
600 class JQueryDateTimePicker(FieldWidget): |
|
601 """Compound widget using :class:`JQueryDatePicker` and |
|
602 :class:`JQueryTimePicker` widgets to define a date and time picker. Will |
|
603 return the date and time as python datetime instance. |
|
604 """ |
440 def __init__(self, initialtime=None, timesteps=15, **kwargs): |
605 def __init__(self, initialtime=None, timesteps=15, **kwargs): |
441 super(JQueryDateTimePicker, self).__init__(**kwargs) |
606 super(JQueryDateTimePicker, self).__init__(**kwargs) |
442 self.initialtime = initialtime |
607 self.initialtime = initialtime |
443 self.timesteps = timesteps |
608 self.timesteps = timesteps |
444 |
609 |
494 attrs.setdefault('cubicweb:wdgtype', wdgtype) |
659 attrs.setdefault('cubicweb:wdgtype', wdgtype) |
495 attrs.setdefault('cubicweb:loadtype', loadtype) |
660 attrs.setdefault('cubicweb:loadtype', loadtype) |
496 |
661 |
497 |
662 |
498 class AjaxWidget(FieldWidget): |
663 class AjaxWidget(FieldWidget): |
499 """simple <div> based ajax widget""" |
664 """Simple <div> based ajax widget, requiring a `wdgtype` argument telling |
|
665 which javascript widget should be used. |
|
666 """ |
500 def __init__(self, wdgtype, inputid=None, **kwargs): |
667 def __init__(self, wdgtype, inputid=None, **kwargs): |
501 super(AjaxWidget, self).__init__(**kwargs) |
668 super(AjaxWidget, self).__init__(**kwargs) |
502 init_ajax_attributes(self.attrs, wdgtype) |
669 init_ajax_attributes(self.attrs, wdgtype) |
503 if inputid is not None: |
670 if inputid is not None: |
504 self.attrs['cubicweb:inputid'] = inputid |
671 self.attrs['cubicweb:inputid'] = inputid |
507 attrs = self.values_and_attributes(form, field)[-1] |
674 attrs = self.values_and_attributes(form, field)[-1] |
508 return tags.div(**attrs) |
675 return tags.div(**attrs) |
509 |
676 |
510 |
677 |
511 class AutoCompletionWidget(TextInput): |
678 class AutoCompletionWidget(TextInput): |
512 """ajax widget for StringField, proposing matching existing values as you |
679 """<input type='text'> based ajax widget, taking a `autocomplete_initfunc` |
513 type. |
680 argument which should specify the name of a method of the json |
|
681 controller. This method is expected to return allowed values for the input, |
|
682 that the widget will use to propose matching values as you type. |
514 """ |
683 """ |
515 needs_js = ('cubicweb.widgets.js', 'jquery.autocomplete.js') |
684 needs_js = ('cubicweb.widgets.js', 'jquery.autocomplete.js') |
516 needs_css = ('jquery.autocomplete.css',) |
685 needs_css = ('jquery.autocomplete.css',) |
517 wdgtype = 'SuggestField' |
686 wdgtype = 'SuggestField' |
518 loadtype = 'auto' |
687 loadtype = 'auto' |
614 ''' |
783 ''' |
615 |
784 |
616 # buttons ###################################################################### |
785 # buttons ###################################################################### |
617 |
786 |
618 class Button(Input): |
787 class Button(Input): |
619 """<input type='button'>, base class for global form buttons |
788 """Simple <input type='button'>, base class for global form buttons. |
620 |
789 |
621 note label is a msgid which will be translated at form generation time, you |
790 Note that `label` is a msgid which will be translated at form generation |
622 should not give an already translated string. |
791 time, you should not give an already translated string. |
623 """ |
792 """ |
624 type = 'button' |
793 type = 'button' |
625 def __init__(self, label=stdmsgs.BUTTON_OK, attrs=None, |
794 def __init__(self, label=stdmsgs.BUTTON_OK, attrs=None, |
626 setdomid=None, settabindex=None, |
795 setdomid=None, settabindex=None, |
627 name='', value='', onclick=None, cwaction=None): |
796 name='', value='', onclick=None, cwaction=None): |
660 return tags.button(img + xml_escape(label), escapecontent=False, |
829 return tags.button(img + xml_escape(label), escapecontent=False, |
661 value=label, type=self.type, **attrs) |
830 value=label, type=self.type, **attrs) |
662 |
831 |
663 |
832 |
664 class SubmitButton(Button): |
833 class SubmitButton(Button): |
665 """<input type='submit'>, main button to submit a form""" |
834 """Simple <input type='submit'>, main button to submit a form""" |
666 type = 'submit' |
835 type = 'submit' |
667 |
836 |
668 |
837 |
669 class ResetButton(Button): |
838 class ResetButton(Button): |
670 """<input type='reset'>, main button to reset a form. |
839 """Simple <input type='reset'>, main button to reset a form. You usually |
671 You usually don't want this. |
840 don't want to use this. |
672 """ |
841 """ |
673 type = 'reset' |
842 type = 'reset' |
674 |
843 |
675 |
844 |
676 class ImgButton(object): |
845 class ImgButton(object): |
677 """<img> wrapped into a <a> tag with href triggering something (usually a |
846 """Simple <img> wrapped into a <a> tag with href triggering something (usually a |
678 javascript call) |
847 javascript call). |
679 |
|
680 note label is a msgid which will be translated at form generation time, you |
|
681 should not give an already translated string. |
|
682 """ |
848 """ |
683 def __init__(self, domid, href, label, imgressource): |
849 def __init__(self, domid, href, label, imgressource): |
684 self.domid = domid |
850 self.domid = domid |
685 self.href = href |
851 self.href = href |
686 self.imgressource = imgressource |
852 self.imgressource = imgressource |
695 'domid': self.domid, 'href': self.href} |
861 'domid': self.domid, 'href': self.href} |
696 |
862 |
697 |
863 |
698 # more widgets ################################################################# |
864 # more widgets ################################################################# |
699 |
865 |
|
866 class IntervalWidget(FieldWidget): |
|
867 """Custom widget to display an interval composed by 2 fields. This widget is |
|
868 expected to be used with a :class:`CompoundField` containing the two actual |
|
869 fields. |
|
870 |
|
871 Exemple usage:: |
|
872 |
|
873 class MyForm(FieldsForm): |
|
874 price = CompoundField(fields=(IntField(name='minprice'), |
|
875 IntField(name='maxprice')), |
|
876 label=_('price'), |
|
877 widget=IntervalWidget()) |
|
878 """ |
|
879 def _render(self, form, field, renderer): |
|
880 actual_fields = field.fields |
|
881 assert len(actual_fields) == 2 |
|
882 return u'<div>%s %s %s %s</div>' % ( |
|
883 form._cw._('from_interval_start'), |
|
884 actual_fields[0].render(form, renderer), |
|
885 form._cw._('to_interval_end'), |
|
886 actual_fields[1].render(form, renderer), |
|
887 ) |
|
888 |
|
889 |
|
890 class HorizontalLayoutWidget(FieldWidget): |
|
891 """Custom widget to display a set of fields grouped together horizontally in |
|
892 a form. See `IntervalWidget` for example usage. |
|
893 """ |
|
894 def _render(self, form, field, renderer): |
|
895 if self.attrs.get('display_label', True): |
|
896 subst = self.attrs.get('label_input_substitution', '%(label)s %(input)s') |
|
897 fields = [subst % {'label': renderer.render_label(form, f), |
|
898 'input': f.render(form, renderer)} |
|
899 for f in field.subfields(form)] |
|
900 else: |
|
901 fields = [f.render(form, renderer) for f in field.subfields(form)] |
|
902 return u'<div>%s</div>' % ' '.join(fields) |
|
903 |
|
904 |
700 class EditableURLWidget(FieldWidget): |
905 class EditableURLWidget(FieldWidget): |
701 """custom widget to edit separatly an url path / query string (used by |
906 """Custom widget to edit separatly an url path / query string (used by |
702 default for Bookmark.path for instance), dealing with url quoting nicely |
907 default for the `path` attribute of `Bookmark` entities). |
703 (eg user edit the unquoted value). |
908 |
704 """ |
909 It deals with url quoting nicely so that the user edit the unquoted value. |
705 |
910 """ |
706 def _render(self, form, field, renderer): |
911 |
707 """render the widget for the given `field` of `form`. |
912 def _render(self, form, field, renderer): |
708 |
|
709 Generate one <input> tag for each field's value |
|
710 """ |
|
711 assert self.suffix is None, 'not supported' |
913 assert self.suffix is None, 'not supported' |
712 req = form._cw |
914 req = form._cw |
713 pathqname = field.input_name(form, 'path') |
915 pathqname = field.input_name(form, 'path') |
714 fqsqname = field.input_name(form, 'fqs') # formatted query string |
916 fqsqname = field.input_name(form, 'fqs') # formatted query string |
715 if pathqname in form.form_previous_values: |
917 if pathqname in form.form_previous_values: |