1 """The bigger TurboGears widgets"""
2
3 __all__ = ['CalendarDatePicker', 'CalendarDateTimePicker',
4 'AutoCompleteField', 'AutoCompleteTextField',
5 'LinkRemoteFunction', 'RemoteForm', 'AjaxGrid', 'URLLink']
6
7 import itertools
8 from datetime import datetime
9
10 from turbogears import validators, expose
11 from turbojson import jsonify
12 from turbogears.widgets.base import (CSSLink, JSLink, CSSSource, JSSource,
13 Widget, WidgetsList, static, mochikit, CoreWD)
14 from turbogears.widgets.i18n import CalendarLangFileLink
15 from turbogears.widgets.forms import (FormField, CompoundFormField, TextField,
16 HiddenField, TableForm, CheckBox, RadioButtonList)
17 from turbogears.widgets.rpc import RPC
21 """Use a Javascript calendar system to allow picking of calendar dates."""
22
23 template = """
24 <span xmlns:py="http://genshi.edgewall.org/" class="${field_class}">
25 <input type="text" id="${field_id}" class="${field_class}" name="${name}" value="${strdate}" py:attrs="attrs"/>
26 <input type="button" id="${field_id}_trigger" class="date_field_button" value="${button_text}"/>
27 <script type="text/javascript">
28 Calendar.setup({
29 inputField: '${field_id}',
30 ifFormat: '${format}',
31 button: '${field_id}_trigger'
32 <span py:if="picker_shows_time" py:replace="', showsTime : true'"/>
33 });
34 </script>
35 </span>
36 """
37 params = ['attrs', 'skin', 'picker_shows_time', 'button_text',
38 'format', 'calendar_lang']
39 params_doc = {
40 'attrs': 'Extra attributes',
41 'skin': 'For alternate skins, such as "calendar-blue" or "skins/aqua/theme"',
42 'picker_shows_time': 'Whether the calendar should let you pick a time, too',
43 'button_text': 'Text for the button that will show the calendar picker',
44 'format': 'The date format (default is mm/dd/yyyy)',
45 'calendar_lang': 'The language to be used in the calendar picker.'
46 }
47 attrs = {}
48 skin = 'calendar-system'
49 picker_shows_time = False
50 button_text = 'Choose'
51 format = '%m/%d/%Y'
52 calendar_lang = None
53 _default = None
54
55 - def __init__(self, name=None, default=None, not_empty=True,
56 calendar_lang=None, validator=None, format=None, **kw):
78
79 @property
81 if self._default is None and self.not_empty:
82 return datetime.now()
83 return self._default
84
86 super(CalendarDatePicker, self).update_params(d)
87 if hasattr(d['value'], 'strftime'):
88 d['strdate'] = d['value'].strftime(d['format'])
89 else:
90 d['strdate'] = d['value']
91
97
100 """Javascript calendar system to allow picking of dates with times."""
101
102 format = '%Y/%m/%d %H:%M'
103 picker_shows_time = True
104
110
113 """Mixin class for autocomplete fields.
114
115 Performs Ajax-style autocompletion by requesting search
116 results from the server as the user types.
117
118 """
119
120 javascript = [mochikit, JSLink(static,"autocompletefield.js")]
121 css = [CSSLink(static,"autocompletefield.css")]
122 params = ['search_controller', 'search_param', 'result_name', 'attrs',
123 'only_suggest', 'complete_delay', 'take_focus', 'min_chars', 'show_spinner']
124 params_doc = {
125 'attrs': 'Extra attributes',
126 'search_controller': 'Name of the controller returning the auto completions',
127 'search_param': 'Name of the search parameter ("*" passes all form fields)',
128 'result_name': 'Name of the result list returned by the controller',
129 'only_suggest': 'If true, pressing enter does not automatically submit the first list item.',
130 'complete_delay': 'Delay (in seconds) before loading new auto completions',
131 'take_focus': 'If true, take focus on load.',
132 'min_chars': 'Minimum number of characters to type before autocomplete activates',
133 'show_spinner': 'If false, the spinner (load indicator) is not shown.'
134 }
135 attrs = {}
136 search_controller = ''
137 search_param = 'searchString'
138 result_name = 'textItems'
139 only_suggest = False
140 complete_delay = 0.200
141 take_focus = False
142 min_chars = 1
143 show_spinner = True
144
147 """Text field with auto complete functionality and hidden key field."""
148
149 template = """
150 <span xmlns:py="http://genshi.edgewall.org/" id="${field_id}" class="${field_class}">
151 <script type="text/javascript">
152 AutoCompleteManager${field_id} = new AutoCompleteManager('${field_id}',
153 '${text_field.field_id}', '${hidden_field.field_id}',
154 '${search_controller}', '${search_param}', '${result_name}',${str(only_suggest).lower()},
155 '${show_spinner and tg.url([tg.widgets, 'turbogears.widgets/spinner.gif']) or None}',
156 ${complete_delay}, ${str(take_focus).lower()}, ${min_chars});
157 addLoadEvent(AutoCompleteManager${field_id}.initialize);
158 </script>
159 ${text_field.display(value_for(text_field), **params_for(text_field))}
160 <img py:if="show_spinner" id="autoCompleteSpinner${field_id}"
161 src="${tg.url([tg.widgets, 'turbogears.widgets/spinnerstopped.png'])}" alt=""/>
162 <span class="autoTextResults" id="autoCompleteResults${field_id}"/>
163 ${hidden_field.display(value_for(hidden_field), **params_for(hidden_field))}
164 </span>
165 """
166 member_widgets = ['text_field', 'hidden_field']
167 text_field = TextField(name='text')
168 hidden_field = HiddenField(name='hidden')
169
172
173 name = "AutoCompleteField"
174 codes = """AK AL AR AS AZ CA CO CT DC DE FL FM GA GU HI IA ID IL IN KS
175 KY LA MA MD ME MH MI MN MO MP MS MT NC ND NE NH NJ NM NV NY OH
176 OK OR PA PR PW RI SC SD TN TX UM UT VA VI VT WA WI WV WY""".split()
177 states = """Alaska Alabama Arkansas American_Samoa Arizona
178 California Colorado Connecticut District_of_Columbia
179 Delaware Florida Federated_States_of_Micronesia Georgia Guam
180 Hawaii Iowa Idaho Illinois Indiana Kansas Kentucky Louisiana
181 Massachusetts Maryland Maine Marshall_Islands Michigan
182 Minnesota Missouri Northern_Mariana_Islands Mississippi
183 Montana North_Carolina North_Dakota Nebraska New_Hampshire
184 New_Jersey New_Mexico Nevada New_York Ohio Oklahoma Oregon
185 Pennsylvania Puerto_Rico Palau Rhode_Island South_Carolina
186 South_Dakota Tennessee Texas U.S._Minor_Outlying_Islands
187 Utah Virginia Virgin_Islands_of_the_U.S. Vermont Washington
188 Wisconsin West_Virginia Wyoming""".split()
189 states = map(lambda s: s.replace('_', ' '), states)
190 state_code = dict(zip(codes, states))
191 template = """
192 <form xmlns:py="http://genshi.edgewall.org/" onsubmit="if (
193 this.elements[0].value && this.elements[1].value)
194 alert('The alpha code for '+this.elements[0].value
195 +' is '+this.elements[1].value+'.');return false"><table>
196 <tr><th>State</th><td py:content="for_widget.display()"/>
197 <td><input type="submit" value="Show alpha code"/></td></tr>
198 </table></form>
199 """
200 full_class_name = "turbogears.widgets.AutoCompleteField"
201
207
208 @expose('json')
210 states = []
211 code = state.upper()
212 if code in self.state_code:
213 states.append((self.state_code[code], code))
214 else:
215 states.extend([s for s in zip(self.states, self.codes)
216 if s[0].lower().startswith(state.lower())])
217 return dict(states=states)
218
219
220 -class AutoCompleteTextField(TextField, AutoComplete):
221 """Text field with auto complete functionality."""
222
223 template = """
224 <span xmlns:py="http://genshi.edgewall.org/" class="${field_class}">
225 <script type="text/javascript">
226 AutoCompleteManager${field_id} = new AutoCompleteManager('${field_id}', '${field_id}', null,
227 '${search_controller}', '${search_param}', '${result_name}', ${str(only_suggest).lower()},
228 '${show_spinner and tg.url([tg.widgets, 'turbogears.widgets/spinner.gif']) or None}',
229 ${complete_delay}, ${str(take_focus).lower()}, ${min_chars});
230 addLoadEvent(AutoCompleteManager${field_id}.initialize);
231 </script>
232 <input type="text" name="${name}" class="${field_class}" id="${field_id}"
233 value="${value}" py:attrs="attrs"/>
234 <img py:if="show_spinner" id="autoCompleteSpinner${field_id}"
235 src="${tg.url([tg.widgets, 'turbogears.widgets/spinnerstopped.png'])}" alt=""/>
236 <span class="autoTextResults" id="autoCompleteResults${field_id}"/>
237 </span>
238 """
239
242
243 name = "AutoCompleteTextField"
244 states = AutoCompleteFieldDesc.states
245 state_code = AutoCompleteFieldDesc.state_code
246 template = """
247 <table xmlns:py="http://genshi.edgewall.org/">
248 <tr><th>State</th><td py:content="for_widget.display()"/></tr>
249 </table>
250 """
251 full_class_name = "turbogears.widgets.AutoCompleteTextField"
252
253 - def __init__(self, *args, **kw):
254 super(AutoCompleteTextFieldDesc, self).__init__(*args, **kw)
255 self.for_widget = AutoCompleteTextField(name="state_only",
256 search_controller='%s/search' % self.full_class_name,
257 search_param='state', result_name='states')
258
259 @expose('json')
260 - def search(self, state):
261 states = []
262 code = state.upper()
263 if code in self.state_code:
264 states.append(self.state_code[code])
265 else:
266 states.extend([s for s in self.states
267 if s.lower().startswith(state.lower())])
268 return dict(states=states)
269
272 """Link with remote execution.
273
274 Returns a link that executes a POST asynchronously
275 and updates a DOM Object with the result of it.
276
277 """
278
279 template = """
280 <a xmlns:py="http://genshi.edgewall.org/" name="${name}"
281 py:attrs="attrs" py:content="value" onclick="${js}" href="#"/>
282 """
283
284 params = ['attrs']
285 attrs = {}
286
289
290 name = "AJAX remote function"
291 states = AutoCompleteFieldDesc.states
292 template = """
293 <div id="items">
294 ${for_widget.display("States starting with the letter 'N'", update="items")}
295 </div>
296 """
297 full_class_name = 'turbogears.widgets.LinkRemoteFunction'
298
304
305 @expose()
307 return '<br/>'.join(
308 [s for s in self.states if s.startswith(state_starts_with)])
309
323
357
358
359 ajaxgridcounter = itertools.count()
362 """AJAX updateable datagrid based on widget.js grid"""
363
364 template = """<div id="${id}" xmlns:py="http://genshi.edgewall.org/">
365 <a py:if="refresh_text"
366 href="#"
367 onclick="javascript:${id}_AjaxGrid.refresh(${defaults});return false;">
368 ${refresh_text}
369 </a>
370 <div id="${id}_update"></div>
371 <script type="text/javascript">
372 addLoadEvent(partial(${id}_AjaxGrid.refresh, ${defaults}));
373 </script>
374 </div>
375 """
376 params = ['refresh_text', 'id', 'defaults']
377 defaults = {}
378 refresh_text = "Update"
379 id = 'ajaxgrid_%d' % ajaxgridcounter.next()
380
381 - def __init__(self, refresh_url, *args, **kw):
382 super(AjaxGrid, self).__init__(*args, **kw)
383 target = '%s_update' % self.id
384 self.javascript = [
385 mochikit,
386 JSLink('turbogears', 'js/widget.js'),
387 JSLink(static, 'ajaxgrid.js'),
388 JSSource("""
389 %(id)s_AjaxGrid = new AjaxGrid('%(refresh_url)s', '%(target)s');
390 """ % dict(id=self.id, refresh_url=refresh_url, target=target)
391 ),
392 ]
393
397
400
401 name = "AJAX Grid"
402 full_class_name = 'turbogears.widgets.AjaxGrid'
403
404 @staticmethod
406 total = 1
407 yield 0, 1
408 for k in xrange(1, n+1):
409 total *= k
410 yield k, total
411
412
421
422 @expose('json')
424 return dict(
425 headers = ['N', 'fact(N)'],
426 rows = list(self.facgen(self.update_count.next())),
427 )
428
431 """Hyperlink"""
432
433 template = """
434 <a xmlns:py="http://genshi.edgewall.org/"
435 href="$link"
436 target="$target"
437 py:attrs="attrs"
438 >$text</a>
439 """
440 params = ['target', 'text', 'link', 'attrs']
441 attrs = {}
442 params_doc = {'link': 'Hyperlink',
443 'target': 'Specify where the link should be opened',
444 'text': 'The message to be shown for the link',
445 'attrs': 'Extra attributes'}
446