95 """ |
95 """ |
96 __docformat__ = "restructuredtext en" |
96 __docformat__ = "restructuredtext en" |
97 |
97 |
98 from functools import reduce |
98 from functools import reduce |
99 from datetime import date |
99 from datetime import date |
100 from warnings import warn |
|
101 |
100 |
102 from six import text_type, string_types |
101 from six import text_type, string_types |
103 |
102 |
104 from logilab.mtconverter import xml_escape |
103 from logilab.mtconverter import xml_escape |
105 from logilab.common.deprecation import deprecated |
|
106 from logilab.common.date import todatetime |
104 from logilab.common.date import todatetime |
107 |
105 |
108 from cubicweb import tags, uilib |
106 from cubicweb import tags, uilib |
109 from cubicweb.utils import json_dumps |
107 from cubicweb.utils import json_dumps |
110 from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError |
108 from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError |
208 :attr:`settabindex` attributes) |
206 :attr:`settabindex` attributes) |
209 """ |
207 """ |
210 attrs = dict(self.attrs) |
208 attrs = dict(self.attrs) |
211 if self.setdomid: |
209 if self.setdomid: |
212 attrs['id'] = field.dom_id(form, self.suffix) |
210 attrs['id'] = field.dom_id(form, self.suffix) |
213 if self.settabindex and not 'tabindex' in attrs: |
211 if self.settabindex and 'tabindex' not in attrs: |
214 attrs['tabindex'] = form._cw.next_tabindex() |
212 attrs['tabindex'] = form._cw.next_tabindex() |
215 if 'placeholder' in attrs: |
213 if 'placeholder' in attrs: |
216 attrs['placeholder'] = form._cw._(attrs['placeholder']) |
214 attrs['placeholder'] = form._cw._(attrs['placeholder']) |
217 return attrs |
215 return attrs |
218 |
216 |
385 class HiddenInput(Input): |
383 class HiddenInput(Input): |
386 """Simple <input type='hidden'> for hidden value, will return a unicode |
384 """Simple <input type='hidden'> for hidden value, will return a unicode |
387 string. |
385 string. |
388 """ |
386 """ |
389 type = 'hidden' |
387 type = 'hidden' |
390 setdomid = False # by default, don't set id attribute on hidden input |
388 setdomid = False # by default, don't set id attribute on hidden input |
391 settabindex = False |
389 settabindex = False |
392 |
390 |
393 |
391 |
394 class ButtonInput(Input): |
392 class ButtonInput(Input): |
395 """Simple <input type='button'>, will return a unicode string. |
393 """Simple <input type='button'>, will return a unicode string. |
472 selected='selected', **oattrs)) |
470 selected='selected', **oattrs)) |
473 else: |
471 else: |
474 options.append(tags.option(label, value=value, **oattrs)) |
472 options.append(tags.option(label, value=value, **oattrs)) |
475 if optgroup_opened: |
473 if optgroup_opened: |
476 options.append(u'</optgroup>') |
474 options.append(u'</optgroup>') |
477 if not 'size' in attrs: |
475 if 'size' not in attrs: |
478 if self._multiple: |
476 if self._multiple: |
479 size = text_type(min(self.default_size, len(vocab) or 1)) |
477 size = text_type(min(self.default_size, len(vocab) or 1)) |
480 else: |
478 else: |
481 size = u'1' |
479 size = u'1' |
482 attrs['size'] = size |
480 attrs['size'] = size |
530 else: |
528 else: |
531 if value not in values: |
529 if value not in values: |
532 options.append(tags.option(label, value=value)) |
530 options.append(tags.option(label, value=value)) |
533 if 'size' not in attrs: |
531 if 'size' not in attrs: |
534 attrs['size'] = self.default_size |
532 attrs['size'] = self.default_size |
535 if 'id' in attrs : |
533 if 'id' in attrs: |
536 attrs.pop('id') |
534 attrs.pop('id') |
537 return tags.select(name=name, multiple=self._multiple, id=name, |
535 return tags.select(name=name, multiple=self._multiple, id=name, |
538 options=options, **attrs) + '\n'.join(inputs) |
536 options=options, **attrs) + '\n'.join(inputs) |
539 |
|
540 |
537 |
541 def _render(self, form, field, renderer): |
538 def _render(self, form, field, renderer): |
542 domid = field.dom_id(form) |
539 domid = field.dom_id(form) |
543 jsnodes = {'widgetid': domid, |
540 jsnodes = {'widgetid': domid, |
544 'from': 'from_' + domid, |
541 'from': 'from_' + domid, |
547 % (jsnodes['widgetid'], jsnodes['from'], jsnodes['to'])) |
544 % (jsnodes['widgetid'], jsnodes['from'], jsnodes['to'])) |
548 field.required = True |
545 field.required = True |
549 return (self.template % |
546 return (self.template % |
550 {'widgetid': jsnodes['widgetid'], |
547 {'widgetid': jsnodes['widgetid'], |
551 # helpinfo select tag |
548 # helpinfo select tag |
552 'inoutinput' : self.render_select(form, field, jsnodes['from']), |
549 'inoutinput': self.render_select(form, field, jsnodes['from']), |
553 # select tag with resultats |
550 # select tag with resultats |
554 'resinput' : self.render_select(form, field, jsnodes['to'], selected=True), |
551 'resinput': self.render_select(form, field, jsnodes['to'], selected=True), |
555 'addinput' : self.add_button % jsnodes, |
552 'addinput': self.add_button % jsnodes, |
556 'removeinput': self.remove_button % jsnodes |
553 'removeinput': self.remove_button % jsnodes |
557 }) |
554 }) |
558 |
555 |
559 |
556 |
560 class BitSelect(Select): |
557 class BitSelect(Select): |
671 year, month = value.year, value.month |
668 year, month = value.year, value.month |
672 return (u"""<a onclick="toggleCalendar('%s', '%s', %s, %s);" class="calhelper"> |
669 return (u"""<a onclick="toggleCalendar('%s', '%s', %s, %s);" class="calhelper"> |
673 <img src="%s" title="%s" alt="" /></a><div class="calpopup hidden" id="%s"></div>""" |
670 <img src="%s" title="%s" alt="" /></a><div class="calpopup hidden" id="%s"></div>""" |
674 % (helperid, inputid, year, month, |
671 % (helperid, inputid, year, month, |
675 form._cw.uiprops['CALENDAR_ICON'], |
672 form._cw.uiprops['CALENDAR_ICON'], |
676 form._cw._('calendar'), helperid) ) |
673 form._cw._('calendar'), helperid)) |
677 |
674 |
678 |
675 |
679 class JQueryDatePicker(FieldWidget): |
676 class JQueryDatePicker(FieldWidget): |
680 """Use jquery.ui.datepicker to define a date picker. Will return the date as |
677 """Use jquery.ui.datepicker to define a date picker. Will return the date as |
681 a unicode string. |
678 a unicode string. |
757 self.separator = separator |
754 self.separator = separator |
758 |
755 |
759 def _render(self, form, field, renderer): |
756 def _render(self, form, field, renderer): |
760 domid = field.dom_id(form, self.suffix) |
757 domid = field.dom_id(form, self.suffix) |
761 form._cw.add_onload(u'cw.jqNode("%s").timePicker({step: %s, separator: "%s"})' % ( |
758 form._cw.add_onload(u'cw.jqNode("%s").timePicker({step: %s, separator: "%s"})' % ( |
762 domid, self.timesteps, self.separator)) |
759 domid, self.timesteps, self.separator)) |
763 return self._render_input(form, field) |
760 return self._render_input(form, field) |
764 |
761 |
765 |
762 |
766 class JQueryDateTimePicker(FieldWidget): |
763 class JQueryDateTimePicker(FieldWidget): |
767 """Compound widget using :class:`JQueryDatePicker` and |
764 """Compound widget using :class:`JQueryDatePicker` and |
797 timestr = req.format_time(self.initialtime) |
794 timestr = req.format_time(self.initialtime) |
798 datepicker = JQueryDatePicker(datestr=datestr, suffix='date') |
795 datepicker = JQueryDatePicker(datestr=datestr, suffix='date') |
799 timepicker = JQueryTimePicker(timestr=timestr, timesteps=self.timesteps, |
796 timepicker = JQueryTimePicker(timestr=timestr, timesteps=self.timesteps, |
800 suffix='time') |
797 suffix='time') |
801 return u'<div id="%s">%s%s</div>' % (field.dom_id(form), |
798 return u'<div id="%s">%s%s</div>' % (field.dom_id(form), |
802 datepicker.render(form, field, renderer), |
799 datepicker.render(form, field, renderer), |
803 timepicker.render(form, field, renderer)) |
800 timepicker.render(form, field, renderer)) |
804 |
801 |
805 def process_field_data(self, form, field): |
802 def process_field_data(self, form, field): |
806 req = form._cw |
803 req = form._cw |
807 datestr = req.form.get(field.input_name(form, 'date')).strip() or None |
804 datestr = req.form.get(field.input_name(form, 'date')).strip() or None |
808 timestr = req.form.get(field.input_name(form, 'time')).strip() or None |
805 timestr = req.form.get(field.input_name(form, 'time')).strip() or None |
885 fname = self.autocomplete_initfunc |
882 fname = self.autocomplete_initfunc |
886 return entity._cw.build_url('ajax', fname=fname, mode='remote', |
883 return entity._cw.build_url('ajax', fname=fname, mode='remote', |
887 pageid=entity._cw.pageid) |
884 pageid=entity._cw.pageid) |
888 |
885 |
889 |
886 |
890 |
|
891 class StaticFileAutoCompletionWidget(AutoCompletionWidget): |
887 class StaticFileAutoCompletionWidget(AutoCompletionWidget): |
892 """XXX describe me""" |
888 """XXX describe me""" |
893 wdgtype = 'StaticFileSuggestField' |
889 wdgtype = 'StaticFileSuggestField' |
894 |
890 |
895 def _get_url(self, entity, field): |
891 def _get_url(self, entity, field): |
904 class LazyRestrictedAutoCompletionWidget(RestrictedAutoCompletionWidget): |
900 class LazyRestrictedAutoCompletionWidget(RestrictedAutoCompletionWidget): |
905 """remote autocomplete """ |
901 """remote autocomplete """ |
906 |
902 |
907 def values_and_attributes(self, form, field): |
903 def values_and_attributes(self, form, field): |
908 """override values_and_attributes to handle initial displayed values""" |
904 """override values_and_attributes to handle initial displayed values""" |
909 values, attrs = super(LazyRestrictedAutoCompletionWidget, self).values_and_attributes(form, field) |
905 values, attrs = super(LazyRestrictedAutoCompletionWidget, self).values_and_attributes( |
|
906 form, field) |
910 assert len(values) == 1, "multiple selection is not supported yet by LazyWidget" |
907 assert len(values) == 1, "multiple selection is not supported yet by LazyWidget" |
911 if not values[0]: |
908 if not values[0]: |
912 values = form.cw_extra_kwargs.get(field.name,'') |
909 values = form.cw_extra_kwargs.get(field.name, '') |
913 if not isinstance(values, (tuple, list)): |
910 if not isinstance(values, (tuple, list)): |
914 values = (values,) |
911 values = (values,) |
915 try: |
912 try: |
916 values = list(values) |
913 values = list(values) |
917 values[0] = int(values[0]) |
914 values[0] = int(values[0]) |
947 return u'<div>%s %s %s %s</div>' % ( |
944 return u'<div>%s %s %s %s</div>' % ( |
948 form._cw._('from_interval_start'), |
945 form._cw._('from_interval_start'), |
949 actual_fields[0].render(form, renderer), |
946 actual_fields[0].render(form, renderer), |
950 form._cw._('to_interval_end'), |
947 form._cw._('to_interval_end'), |
951 actual_fields[1].render(form, renderer), |
948 actual_fields[1].render(form, renderer), |
952 ) |
949 ) |
953 |
950 |
954 |
951 |
955 class HorizontalLayoutWidget(FieldWidget): |
952 class HorizontalLayoutWidget(FieldWidget): |
956 """Custom widget to display a set of fields grouped together horizontally in |
953 """Custom widget to display a set of fields grouped together horizontally in |
957 a form. See `IntervalWidget` for example usage. |
954 a form. See `IntervalWidget` for example usage. |
958 """ |
955 """ |
959 def _render(self, form, field, renderer): |
956 def _render(self, form, field, renderer): |
960 if self.attrs.get('display_label', True): |
957 if self.attrs.get('display_label', True): |
961 subst = self.attrs.get('label_input_substitution', '%(label)s %(input)s') |
958 subst = self.attrs.get('label_input_substitution', '%(label)s %(input)s') |
962 fields = [subst % {'label': renderer.render_label(form, f), |
959 fields = [subst % {'label': renderer.render_label(form, f), |
963 'input': f.render(form, renderer)} |
960 'input': f.render(form, renderer)} |
964 for f in field.subfields(form)] |
961 for f in field.subfields(form)] |
965 else: |
962 else: |
966 fields = [f.render(form, renderer) for f in field.subfields(form)] |
963 fields = [f.render(form, renderer) for f in field.subfields(form)] |
967 return u'<div>%s</div>' % ' '.join(fields) |
964 return u'<div>%s</div>' % ' '.join(fields) |
968 |
965 |
976 |
973 |
977 def _render(self, form, field, renderer): |
974 def _render(self, form, field, renderer): |
978 assert self.suffix is None, 'not supported' |
975 assert self.suffix is None, 'not supported' |
979 req = form._cw |
976 req = form._cw |
980 pathqname = field.input_name(form, 'path') |
977 pathqname = field.input_name(form, 'path') |
981 fqsqname = field.input_name(form, 'fqs') # formatted query string |
978 fqsqname = field.input_name(form, 'fqs') # formatted query string |
982 if pathqname in form.form_previous_values: |
979 if pathqname in form.form_previous_values: |
983 path = form.form_previous_values[pathqname] |
980 path = form.form_previous_values[pathqname] |
984 fqs = form.form_previous_values[fqsqname] |
981 fqs = form.form_previous_values[fqsqname] |
985 else: |
982 else: |
986 if field.name in req.form: |
983 if field.name in req.form: |
997 path = qs = '' |
994 path = qs = '' |
998 fqs = u'\n'.join(u'%s=%s' % (k, v) for k, v in req.url_parse_qsl(qs)) |
995 fqs = u'\n'.join(u'%s=%s' % (k, v) for k, v in req.url_parse_qsl(qs)) |
999 attrs = dict(self.attrs) |
996 attrs = dict(self.attrs) |
1000 if self.setdomid: |
997 if self.setdomid: |
1001 attrs['id'] = field.dom_id(form) |
998 attrs['id'] = field.dom_id(form) |
1002 if self.settabindex and not 'tabindex' in attrs: |
999 if self.settabindex and 'tabindex' not in attrs: |
1003 attrs['tabindex'] = req.next_tabindex() |
1000 attrs['tabindex'] = req.next_tabindex() |
1004 # ensure something is rendered |
1001 # ensure something is rendered |
1005 inputs = [u'<table><tr><th>', |
1002 inputs = [u'<table><tr><th>', |
1006 req._('i18n_bookmark_url_path'), |
1003 req._('i18n_bookmark_url_path'), |
1007 u'</th><td>', |
1004 u'</th><td>', |
1037 line = line.strip() |
1034 line = line.strip() |
1038 if line: |
1035 if line: |
1039 try: |
1036 try: |
1040 key, val = line.split('=', 1) |
1037 key, val = line.split('=', 1) |
1041 except ValueError: |
1038 except ValueError: |
1042 raise ProcessFormError(req._("wrong query parameter line %s") % (i+1)) |
1039 msg = req._("wrong query parameter line %s") % (i + 1) |
|
1040 raise ProcessFormError(msg) |
1043 # value will be url quoted by build_url_params |
1041 # value will be url quoted by build_url_params |
1044 values.setdefault(key, []).append(val) |
1042 values.setdefault(key, []).append(val) |
1045 if not values: |
1043 if not values: |
1046 return path |
1044 return path |
1047 return u'%s?%s' % (path, req.build_url_params(**values)) |
1045 return u'%s?%s' % (path, req.build_url_params(**values)) |
1085 attrs['onclick'] = self.onclick |
1083 attrs['onclick'] = self.onclick |
1086 if self.name: |
1084 if self.name: |
1087 attrs['name'] = self.name |
1085 attrs['name'] = self.name |
1088 if self.setdomid: |
1086 if self.setdomid: |
1089 attrs['id'] = self.name |
1087 attrs['id'] = self.name |
1090 if self.settabindex and not 'tabindex' in attrs: |
1088 if self.settabindex and 'tabindex' not in attrs: |
1091 attrs['tabindex'] = form._cw.next_tabindex() |
1089 attrs['tabindex'] = form._cw.next_tabindex() |
1092 if self.icon: |
1090 if self.icon: |
1093 img = tags.img(src=form._cw.uiprops[self.icon], alt=self.icon) |
1091 img = tags.img(src=form._cw.uiprops[self.icon], alt=self.icon) |
1094 else: |
1092 else: |
1095 img = u'' |
1093 img = u'' |
1122 def render(self, form, field=None, renderer=None): |
1120 def render(self, form, field=None, renderer=None): |
1123 label = form._cw._(self.label) |
1121 label = form._cw._(self.label) |
1124 imgsrc = form._cw.uiprops[self.imgressource] |
1122 imgsrc = form._cw.uiprops[self.imgressource] |
1125 return '<a id="%(domid)s" href="%(href)s">'\ |
1123 return '<a id="%(domid)s" href="%(href)s">'\ |
1126 '<img src="%(imgsrc)s" alt="%(label)s"/>%(label)s</a>' % { |
1124 '<img src="%(imgsrc)s" alt="%(label)s"/>%(label)s</a>' % { |
1127 'label': label, 'imgsrc': imgsrc, |
1125 'label': label, 'imgsrc': imgsrc, |
1128 'domid': self.domid, 'href': self.href} |
1126 'domid': self.domid, 'href': self.href} |