9 from datetime import date |
9 from datetime import date |
10 |
10 |
11 from cubicweb.common import tags |
11 from cubicweb.common import tags |
12 from cubicweb.web import stdmsgs |
12 from cubicweb.web import stdmsgs |
13 |
13 |
|
14 |
14 class FieldWidget(object): |
15 class FieldWidget(object): |
|
16 """abstract widget class""" |
|
17 # javascript / css files required by the widget |
15 needs_js = () |
18 needs_js = () |
16 needs_css = () |
19 needs_css = () |
|
20 # automatically set id and tabindex attributes ? |
17 setdomid = True |
21 setdomid = True |
18 settabindex = True |
22 settabindex = True |
19 |
23 |
20 def __init__(self, attrs=None, setdomid=None, settabindex=None): |
24 def __init__(self, attrs=None, setdomid=None, settabindex=None): |
21 self.attrs = attrs or {} |
25 if attrs is None: |
|
26 attrs = {} |
|
27 self.attrs = attrs |
22 if setdomid is not None: |
28 if setdomid is not None: |
23 # override class's default value |
29 # override class's default value |
24 self.setdomid = setdomid |
30 self.setdomid = setdomid |
25 if settabindex is not None: |
31 if settabindex is not None: |
26 # override class's default value |
32 # override class's default value |
32 form.req.add_js(self.needs_js) |
38 form.req.add_js(self.needs_js) |
33 if self.needs_css: |
39 if self.needs_css: |
34 form.req.add_css(self.needs_css) |
40 form.req.add_css(self.needs_css) |
35 |
41 |
36 def render(self, form, field): |
42 def render(self, form, field): |
|
43 """render the widget for the given `field` of `form`. |
|
44 To override in concrete class |
|
45 """ |
37 raise NotImplementedError |
46 raise NotImplementedError |
38 |
47 |
39 def _render_attrs(self, form, field): |
48 def _render_attrs(self, form, field): |
|
49 """return html tag name, attributes and a list of values for the field |
|
50 """ |
40 name = form.context[field]['name'] |
51 name = form.context[field]['name'] |
41 values = form.context[field]['value'] |
52 values = form.context[field]['value'] |
42 if not isinstance(values, (tuple, list)): |
53 if not isinstance(values, (tuple, list)): |
43 values = (values,) |
54 values = (values,) |
44 attrs = dict(self.attrs) |
55 attrs = dict(self.attrs) |
48 attrs['tabindex'] = form.req.next_tabindex() |
59 attrs['tabindex'] = form.req.next_tabindex() |
49 return name, values, attrs |
60 return name, values, attrs |
50 |
61 |
51 |
62 |
52 class Input(FieldWidget): |
63 class Input(FieldWidget): |
|
64 """abstract widget class for <input> tag based widgets""" |
53 type = None |
65 type = None |
54 |
66 |
55 def render(self, form, field): |
67 def render(self, form, field): |
|
68 """render the widget for the given `field` of `form`. |
|
69 |
|
70 Generate one <input> tag for each field's value |
|
71 """ |
56 self.add_media(form) |
72 self.add_media(form) |
57 name, values, attrs = self._render_attrs(form, field) |
73 name, values, attrs = self._render_attrs(form, field) |
58 inputs = [tags.input(name=name, value=value, type=self.type, **attrs) |
74 inputs = [tags.input(name=name, value=value, type=self.type, **attrs) |
59 for value in values] |
75 for value in values] |
60 return u'\n'.join(inputs) |
76 return u'\n'.join(inputs) |
61 |
77 |
62 |
78 |
|
79 # basic html widgets ########################################################### |
|
80 |
63 class TextInput(Input): |
81 class TextInput(Input): |
|
82 """<input type='text'>""" |
64 type = 'text' |
83 type = 'text' |
65 |
84 |
66 |
85 |
67 class PasswordInput(Input): |
86 class PasswordInput(Input): |
|
87 """<input type='password'> and its confirmation field (using |
|
88 <field's name>-confirm as name) |
|
89 """ |
68 type = 'password' |
90 type = 'password' |
69 |
91 |
70 def render(self, form, field): |
92 def render(self, form, field): |
71 self.add_media(form) |
93 self.add_media(form) |
72 name, values, attrs = self._render_attrs(form, field) |
94 name, values, attrs = self._render_attrs(form, field) |
83 **{'class': 'emphasis'})] |
105 **{'class': 'emphasis'})] |
84 return u'\n'.join(inputs) |
106 return u'\n'.join(inputs) |
85 |
107 |
86 |
108 |
87 class FileInput(Input): |
109 class FileInput(Input): |
|
110 """<input type='file'>""" |
88 type = 'file' |
111 type = 'file' |
89 |
112 |
90 def _render_attrs(self, form, field): |
113 def _render_attrs(self, form, field): |
91 # ignore value which makes no sense here (XXX even on form validation error?) |
114 # ignore value which makes no sense here (XXX even on form validation error?) |
92 name, values, attrs = super(FileInput, self)._render_attrs(form, field) |
115 name, values, attrs = super(FileInput, self)._render_attrs(form, field) |
93 return name, ('',), attrs |
116 return name, ('',), attrs |
94 |
117 |
95 |
118 |
96 class HiddenInput(Input): |
119 class HiddenInput(Input): |
|
120 """<input type='hidden'>""" |
97 type = 'hidden' |
121 type = 'hidden' |
98 setdomid = False # by default, don't set id attribute on hidden input |
122 setdomid = False # by default, don't set id attribute on hidden input |
99 settabindex = False |
123 settabindex = False |
100 |
124 |
101 |
125 |
102 class ButtonInput(Input): |
126 class ButtonInput(Input): |
|
127 """<input type='button'> |
|
128 |
|
129 if you want a global form button, look at the Button, SubmitButton, |
|
130 ResetButton and ImgButton classes below. |
|
131 """ |
103 type = 'button' |
132 type = 'button' |
104 |
133 |
105 |
134 |
106 class TextArea(FieldWidget): |
135 class TextArea(FieldWidget): |
|
136 """<textarea>""" |
107 def render(self, form, field): |
137 def render(self, form, field): |
108 name, values, attrs = self._render_attrs(form, field) |
138 name, values, attrs = self._render_attrs(form, field) |
109 attrs.setdefault('onkeypress', 'autogrow(this)') |
139 attrs.setdefault('onkeypress', 'autogrow(this)') |
110 if not values: |
140 if not values: |
111 value = u'' |
141 value = u'' |
115 raise ValueError('a textarea is not supposed to be multivalued') |
145 raise ValueError('a textarea is not supposed to be multivalued') |
116 return tags.textarea(value, name=name, **attrs) |
146 return tags.textarea(value, name=name, **attrs) |
117 |
147 |
118 |
148 |
119 class FCKEditor(TextArea): |
149 class FCKEditor(TextArea): |
|
150 """FCKEditor enabled <textarea>""" |
120 def __init__(self, *args, **kwargs): |
151 def __init__(self, *args, **kwargs): |
121 super(FCKEditor, self).__init__(*args, **kwargs) |
152 super(FCKEditor, self).__init__(*args, **kwargs) |
122 self.attrs['cubicweb:type'] = 'wysiwyg' |
153 self.attrs['cubicweb:type'] = 'wysiwyg' |
123 |
154 |
124 def render(self, form, field): |
155 def render(self, form, field): |
125 form.req.fckeditor_config() |
156 form.req.fckeditor_config() |
126 return super(FCKEditor, self).render(form, field) |
157 return super(FCKEditor, self).render(form, field) |
127 |
158 |
128 |
159 |
129 class Select(FieldWidget): |
160 class Select(FieldWidget): |
|
161 """<select>, for field having a specific vocabulary""" |
130 def __init__(self, attrs=None, multiple=False): |
162 def __init__(self, attrs=None, multiple=False): |
131 super(Select, self).__init__(attrs) |
163 super(Select, self).__init__(attrs) |
132 self.multiple = multiple |
164 self.multiple = multiple |
133 |
165 |
134 def render(self, form, field): |
166 def render(self, form, field): |
142 return tags.select(name=name, multiple=self.multiple, |
174 return tags.select(name=name, multiple=self.multiple, |
143 options=options, **attrs) |
175 options=options, **attrs) |
144 |
176 |
145 |
177 |
146 class CheckBox(Input): |
178 class CheckBox(Input): |
|
179 """<input type='checkbox'>, for field having a specific vocabulary. One |
|
180 input will be generated for each possible value. |
|
181 """ |
147 type = 'checkbox' |
182 type = 'checkbox' |
148 |
183 |
149 def render(self, form, field): |
184 def render(self, form, field): |
150 name, curvalues, attrs = self._render_attrs(form, field) |
185 name, curvalues, attrs = self._render_attrs(form, field) |
151 options = [] |
186 options = [] |
159 options.append(tag + label) |
194 options.append(tag + label) |
160 return '<br/>\n'.join(options) |
195 return '<br/>\n'.join(options) |
161 |
196 |
162 |
197 |
163 class Radio(Input): |
198 class Radio(Input): |
|
199 """<input type='radio'>, for field having a specific vocabulary. One |
|
200 input will be generated for each possible value. |
|
201 """ |
164 type = 'radio' |
202 type = 'radio' |
165 setdomid = False |
203 setdomid = False # by default, don't set id attribute on radio input |
166 |
204 |
167 def render(self, form, field): |
205 def render(self, form, field): |
168 name, curvalues, attrs = self._render_attrs(form, field) |
206 name, curvalues, attrs = self._render_attrs(form, field) |
169 options = [] |
207 options = [] |
170 for label, value in field.vocabulary(form): |
208 for label, value in field.vocabulary(form): |
176 tag += label + '<br/>' |
214 tag += label + '<br/>' |
177 options.append(tag) |
215 options.append(tag) |
178 return '\n'.join(options) |
216 return '\n'.join(options) |
179 |
217 |
180 |
218 |
|
219 # javascript widgets ########################################################### |
|
220 |
181 class DateTimePicker(TextInput): |
221 class DateTimePicker(TextInput): |
|
222 """<input type='text' + javascript date/time picker for date or datetime |
|
223 fields |
|
224 """ |
182 monthnames = ('january', 'february', 'march', 'april', |
225 monthnames = ('january', 'february', 'march', 'april', |
183 'may', 'june', 'july', 'august', |
226 'may', 'june', 'july', 'august', |
184 'september', 'october', 'november', 'december') |
227 'september', 'october', 'november', 'december') |
185 daynames = ('monday', 'tuesday', 'wednesday', 'thursday', |
228 daynames = ('monday', 'tuesday', 'wednesday', 'thursday', |
186 'friday', 'saturday', 'sunday') |
229 'friday', 'saturday', 'sunday') |
217 % (helperid, inputid, year, month, |
260 % (helperid, inputid, year, month, |
218 req.external_resource('CALENDAR_ICON'), |
261 req.external_resource('CALENDAR_ICON'), |
219 req._('calendar'), helperid) ) |
262 req._('calendar'), helperid) ) |
220 |
263 |
221 |
264 |
|
265 # ajax widgets ################################################################ |
|
266 |
222 class AjaxWidget(FieldWidget): |
267 class AjaxWidget(FieldWidget): |
|
268 """simple <div> based ajax widget""" |
223 def __init__(self, wdgtype, inputid=None, **kwargs): |
269 def __init__(self, wdgtype, inputid=None, **kwargs): |
224 super(AjaxWidget, self).__init__(**kwargs) |
270 super(AjaxWidget, self).__init__(**kwargs) |
225 self.attrs.setdefault('class', 'widget') |
271 self.attrs.setdefault('class', 'widget') |
226 self.attrs.setdefault('cubicweb:loadtype', 'auto') |
272 self.attrs.setdefault('cubicweb:loadtype', 'auto') |
227 self.attrs['cubicweb:wdgtype'] = wdgtype |
273 self.attrs['cubicweb:wdgtype'] = wdgtype |
231 def render(self, form, field): |
277 def render(self, form, field): |
232 self.add_media(form) |
278 self.add_media(form) |
233 attrs = self._render_attrs(form, field)[-1] |
279 attrs = self._render_attrs(form, field)[-1] |
234 return tags.div(**attrs) |
280 return tags.div(**attrs) |
235 |
281 |
|
282 |
|
283 class AutoCompletionWidget(TextInput): |
|
284 """ajax widget for StringField, proposing matching existing values as you |
|
285 type. |
|
286 """ |
|
287 needs_js = ('cubicweb.widgets.js', 'jquery.autocomplete.js') |
|
288 needs_css = ('jquery.autocomplete.css',) |
|
289 wdgtype = 'SuggestField' |
|
290 loadtype = 'auto' |
|
291 |
|
292 def _render_attrs(self, form, field): |
|
293 name, values, attrs = super(AutoCompletionWidget, self)._render_attrs(form, field) |
|
294 if not values[0]: |
|
295 values = (INTERNAL_FIELD_VALUE,) |
|
296 try: |
|
297 attrs['klass'] += ' widget' |
|
298 except KeyError: |
|
299 attrs['klass'] = 'widget' |
|
300 attrs.setdefault('cubicweb:wdgtype', self.wdgtype) |
|
301 attrs.setdefault('cubicweb:loadtype', self.loadtype) |
|
302 # XXX entity form specific |
|
303 attrs['cubicweb:dataurl'] = self._get_url(form.edited_entity) |
|
304 return name, values, attrs |
|
305 |
|
306 def _get_url(self, entity): |
|
307 return entity.req.build_url('json', fname=entity.autocomplete_initfuncs[self.rschema], |
|
308 pageid=entity.req.pageid, mode='remote') |
|
309 |
|
310 |
|
311 class StaticFileAutoCompletionWidget(AutoCompletionWidget): |
|
312 """XXX describe me""" |
|
313 wdgtype = 'StaticFileSuggestField' |
|
314 |
|
315 def _get_url(self, entity): |
|
316 return entity.req.datadir_url + entity.autocomplete_initfuncs[self.rschema] |
|
317 |
|
318 |
|
319 class RestrictedAutoCompletionWidget(AutoCompletionWidget): |
|
320 """XXX describe me""" |
|
321 wdgtype = 'RestrictedSuggestField' |
|
322 |
|
323 |
|
324 # buttons ###################################################################### |
236 |
325 |
237 class Button(Input): |
326 class Button(Input): |
|
327 """<input type='button'>, base class for global form buttons""" |
238 type = 'button' |
328 type = 'button' |
239 def __init__(self, label=stdmsgs.BUTTON_OK, attrs=None, |
329 def __init__(self, label=stdmsgs.BUTTON_OK, attrs=None, |
240 setdomid=None, settabindex=None, |
330 setdomid=None, settabindex=None, |
241 name='', value='', onclick=None, cwaction=None): |
331 name='', value='', onclick=None, cwaction=None): |
242 super(Button, self).__init__(attrs, setdomid, settabindex) |
332 super(Button, self).__init__(attrs, setdomid, settabindex) |
264 attrs['tabindex'] = form.req.next_tabindex() |
354 attrs['tabindex'] = form.req.next_tabindex() |
265 return tags.input(value=label, type=self.type, **attrs) |
355 return tags.input(value=label, type=self.type, **attrs) |
266 |
356 |
267 |
357 |
268 class SubmitButton(Button): |
358 class SubmitButton(Button): |
|
359 """<input type='submit'>, main button to submit a form""" |
269 type = 'submit' |
360 type = 'submit' |
|
361 |
270 |
362 |
271 class ResetButton(Button): |
363 class ResetButton(Button): |
|
364 """<input type='reset'>, main button to reset a form. |
|
365 You usually don't want this. |
|
366 """ |
272 type = 'reset' |
367 type = 'reset' |
273 |
368 |
|
369 |
274 class ImgButton(object): |
370 class ImgButton(object): |
|
371 """<img> wrapped into a <a> tag with href triggering something (usually a |
|
372 javascript call) |
|
373 """ |
275 def __init__(self, domid, href, label, imgressource): |
374 def __init__(self, domid, href, label, imgressource): |
276 self.domid = domid |
375 self.domid = domid |
277 self.href = href |
376 self.href = href |
278 self.imgressource = imgressource |
377 self.imgressource = imgressource |
279 self.label = label |
378 self.label = label |
281 def render(self, form, field=None): |
380 def render(self, form, field=None): |
282 self.imgsrc = form.req.external_resource(self.imgressource) |
381 self.imgsrc = form.req.external_resource(self.imgressource) |
283 return '<a id="%(domid)s" href="%(href)s"><img src="%(imgsrc)s" alt="%(label)s"/>%(label)s</a>' % self.__dict__ |
382 return '<a id="%(domid)s" href="%(href)s"><img src="%(imgsrc)s" alt="%(label)s"/>%(label)s</a>' % self.__dict__ |
284 |
383 |
285 |
384 |
286 # XXX EntityLinkComboBoxWidget, AddComboBoxWidget, AutoCompletionWidget, |
385 # XXX EntityLinkComboBoxWidget, AddComboBoxWidget, RawDynamicComboBoxWidget |
287 # StaticFileAutoCompletionWidget, RestrictedAutoCompletionWidget... |
|