|
1 """widget classes for form construction |
|
2 |
|
3 :organization: Logilab |
|
4 :copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
6 """ |
|
7 __docformat__ = "restructuredtext en" |
|
8 |
|
9 from cubicweb.common import tags |
|
10 |
|
11 class FieldWidget(object): |
|
12 needs_js = () |
|
13 needs_css = () |
|
14 setdomid = True |
|
15 settabindex = True |
|
16 |
|
17 def __init__(self, attrs=None, setdomid=None, settabindex=None): |
|
18 self.attrs = attrs or {} |
|
19 if setdomid is not None: |
|
20 # override class's default value |
|
21 self.setdomid = setdomid |
|
22 if settabindex is not None: |
|
23 # override class's default value |
|
24 self.settabindex = settabindex |
|
25 |
|
26 def add_media(self, form): |
|
27 """adds media (CSS & JS) required by this widget""" |
|
28 req = form.req |
|
29 if self.needs_js: |
|
30 req.add_js(self.needs_js) |
|
31 if self.needs_css: |
|
32 req.add_css(self.needs_css) |
|
33 |
|
34 def render(self, form, field): |
|
35 raise NotImplementedError |
|
36 |
|
37 def _render_attrs(self, form, field): |
|
38 name = form.context[field]['name'] |
|
39 values = form.context[field]['value'] |
|
40 if not isinstance(values, (tuple, list)): |
|
41 values = (values,) |
|
42 attrs = dict(self.attrs) |
|
43 if self.setdomid: |
|
44 attrs['id'] = form.context[field]['id'] |
|
45 if self.settabindex: |
|
46 attrs['tabindex'] = form.req.next_tabindex() |
|
47 return name, values, attrs |
|
48 |
|
49 class Input(FieldWidget): |
|
50 type = None |
|
51 |
|
52 def render(self, form, field): |
|
53 self.add_media(form) |
|
54 name, values, attrs = self._render_attrs(form, field) |
|
55 inputs = [tags.input(name=name, value=value, type=self.type, **attrs) |
|
56 for value in values] |
|
57 return u'\n'.join(inputs) |
|
58 |
|
59 class TextInput(Input): |
|
60 type = 'text' |
|
61 |
|
62 class PasswordInput(Input): |
|
63 type = 'password' |
|
64 |
|
65 def render(self, form, field): |
|
66 self.add_media(form) |
|
67 name, values, attrs = self._render_attrs(form, field) |
|
68 assert len(values) == 1 |
|
69 inputs = [tags.input(name=name, value=values[0], type=self.type, **attrs), |
|
70 '<br/>', |
|
71 tags.input(name=name+'-confirm', type=self.type, **attrs), |
|
72 ' ', tags.span(form.req._('confirm password'), |
|
73 **{'class': 'emphasis'})] |
|
74 return u'\n'.join(inputs) |
|
75 |
|
76 class FileInput(Input): |
|
77 type = 'file' |
|
78 |
|
79 def _render_attrs(self, form, field): |
|
80 # ignore value which makes no sense here (XXX even on form validation error?) |
|
81 name, values, attrs = super(FileInput, self)._render_attrs(form, field) |
|
82 return name, ('',), attrs |
|
83 |
|
84 class HiddenInput(Input): |
|
85 type = 'hidden' |
|
86 setdomid = False # by default, don't set id attribute on hidden input |
|
87 settabindex = False |
|
88 |
|
89 class ButtonInput(Input): |
|
90 type = 'button' |
|
91 |
|
92 class TextArea(FieldWidget): |
|
93 def render(self, form, field): |
|
94 name, values, attrs = self._render_attrs(form, field) |
|
95 attrs.setdefault('onkeypress', 'autogrow(this)') |
|
96 if not values: |
|
97 value = u'' |
|
98 elif len(values) == 1: |
|
99 value = values[0] |
|
100 else: |
|
101 raise ValueError('a textarea is not supposed to be multivalued') |
|
102 return tags.textarea(value, name=name, **attrs) |
|
103 |
|
104 |
|
105 class FCKEditor(TextArea): |
|
106 def __init__(self, *args, **kwargs): |
|
107 super(FCKEditor, self).__init__(*args, **kwargs) |
|
108 self.attrs['cubicweb:type'] = 'wysiwyg' |
|
109 |
|
110 def render(self, form, field): |
|
111 form.req.fckeditor_config() |
|
112 return super(FCKEditor, self).render(form, field) |
|
113 |
|
114 |
|
115 class Select(FieldWidget): |
|
116 def __init__(self, attrs=None, multiple=False): |
|
117 super(Select, self).__init__(attrs) |
|
118 self.multiple = multiple |
|
119 |
|
120 def render(self, form, field): |
|
121 name, curvalues, attrs = self._render_attrs(form, field) |
|
122 options = [] |
|
123 for label, value in field.vocabulary(form): |
|
124 if value in curvalues: |
|
125 options.append(tags.option(label, value=value, selected='selected')) |
|
126 else: |
|
127 options.append(tags.option(label, value=value)) |
|
128 return tags.select(name=name, multiple=self.multiple, |
|
129 options=options, **attrs) |
|
130 |
|
131 |
|
132 class CheckBox(Input): |
|
133 type = 'checkbox' |
|
134 |
|
135 def render(self, form, field): |
|
136 name, curvalues, attrs = self._render_attrs(form, field) |
|
137 options = [] |
|
138 for label, value in field.vocabulary(form): |
|
139 if value in curvalues: |
|
140 tag = tags.input(name=name, value=value, type=self.type, |
|
141 checked='checked', **attrs) |
|
142 else: |
|
143 tag = tags.input(name=name, value=value, type=self.type, |
|
144 **attrs) |
|
145 options.append(tag + label) |
|
146 return '<br/>\n'.join(options) |
|
147 |
|
148 |
|
149 class Radio(Input): |
|
150 type = 'radio' |
|
151 setdomid = False |
|
152 |
|
153 def render(self, form, field): |
|
154 name, curvalues, attrs = self._render_attrs(form, field) |
|
155 options = [] |
|
156 for label, value in field.vocabulary(form): |
|
157 if value in curvalues: |
|
158 options.append(tags.input(name=name, type=self.type, value=value, checked='checked', **attrs)) |
|
159 else: |
|
160 options.append(tags.option(name=name, type=self.type, value=value, **attrs)) |
|
161 options[-1] += label + '<br/>' |
|
162 return '\n'.join(options) |
|
163 |
|
164 |
|
165 class DateTimePicker(TextInput): |
|
166 monthnames = ('january', 'february', 'march', 'april', |
|
167 'may', 'june', 'july', 'august', |
|
168 'september', 'october', 'november', 'december') |
|
169 daynames = ('monday', 'tuesday', 'wednesday', 'thursday', |
|
170 'friday', 'saturday', 'sunday') |
|
171 |
|
172 needs_js = ('cubicweb.ajax.js', 'cubicweb.calendar.js') |
|
173 needs_css = ('cubicweb.calendar_popup.css',) |
|
174 |
|
175 @classmethod |
|
176 def add_localized_infos(cls, req): |
|
177 """inserts JS variables defining localized months and days""" |
|
178 # import here to avoid dependancy from cubicweb-common to simplejson |
|
179 _ = req._ |
|
180 monthnames = [_(mname) for mname in cls.monthnames] |
|
181 daynames = [_(dname) for dname in cls.daynames] |
|
182 req.html_headers.define_var('MONTHNAMES', monthnames) |
|
183 req.html_headers.define_var('DAYNAMES', daynames) |
|
184 |
|
185 def render(self, form, field): |
|
186 txtwidget = super(DateTimePicker, self).render(form, field) |
|
187 self.add_localized_infos(form.req) |
|
188 cal_button = self._render_calendar_popup(form, field) |
|
189 return txtwidget + cal_button |
|
190 |
|
191 def _render_calendar_popup(self, form, field): |
|
192 req = form.req |
|
193 value = form.context[field]['rawvalue'] |
|
194 inputid = form.context[field]['id'] |
|
195 helperid = '%shelper' % inputid |
|
196 if not value: |
|
197 value = date.today() |
|
198 year, month = value.year, value.month |
|
199 onclick = "toggleCalendar('%s', '%s', %s, %s);" % ( |
|
200 helperid, inputid, year, month) |
|
201 return (u"""<a onclick="toggleCalendar('%s', '%s', %s, %s);" class="calhelper"> |
|
202 <img src="%s" title="%s" alt="" /></a><div class="calpopup hidden" id="%s"></div>""" |
|
203 % (helperid, inputid, year, month, |
|
204 req.external_resource('CALENDAR_ICON'), |
|
205 req._('calendar'), helperid) ) |
|
206 |
|
207 |
|
208 class AjaxWidget(FieldWidget): |
|
209 def __init__(self, wdgtype, inputid=None, **kwargs): |
|
210 super(AjaxWidget, self).__init__(**kwargs) |
|
211 self.attrs.setdefault('class', 'widget') |
|
212 self.attrs.setdefault('cubicweb:loadtype', 'auto') |
|
213 self.attrs['cubicweb:wdgtype'] = wdgtype |
|
214 if inputid is not None: |
|
215 self.attrs['cubicweb:inputid'] = inputid |
|
216 |
|
217 def render(self, form, field): |
|
218 self.add_media(form) |
|
219 name, values, attrs = self._render_attrs(form, field) |
|
220 return tags.div(**attrs) |