Added ability to add custom tooltips and UI improvements.
authorDaniel Diniz <ajaksu@gmail.com>
Mon, 06 Jul 2009 16:13:27 +0200
changeset 2560 a944c0169ad8
parent 2559 af2874bc01f3
child 2561 2751a2462bb3
Added ability to add custom tooltips and UI improvements. Reviewed by: Lennard de Rijk
app/soc/content/css/soc-090627.css
app/soc/content/js/survey-edit-090703.js
app/soc/templates/soc/survey/universal_choice_editor.html
app/soc/views/helper/surveys.py
app/soc/views/models/survey.py
--- a/app/soc/content/css/soc-090627.css	Mon Jul 06 15:06:05 2009 +0200
+++ b/app/soc/content/css/soc-090627.css	Mon Jul 06 16:13:27 2009 +0200
@@ -654,7 +654,6 @@
   border: 5px solid #F7CA75;
   padding: 15px;
   /*max-width: 700px;*/
-  float: left;
 }
 
 
@@ -662,9 +661,8 @@
 /* TODO(ajaksu) remove if unnecessary */
 }
 
-div #survey_widget tr {
-  float: left;
-  clear: both;
+div #survey_widget table {
+  width:100%
 }
 
 div #survey_widget th,
@@ -674,9 +672,14 @@
 
 /* fields */
 
-div #survey_widget textarea {
+div #survey_widget textarea.long_answer {
   padding: 10px;
-  width: 300px;
+  width: 500px;
+}
+
+div #survey_widget textarea.tooltip_entry {
+  padding: 10px;
+  width: 80%;
 }
 
 div #survey_widget input.text_question {
@@ -748,19 +751,15 @@
 div #survey_widget th,
 div #survey_widget td,
 div #survey_widget select {
-  max-width:700px;
 }
 
 div #survey_widget td {
-  display: block;
 }
 
 div #survey_widget label {
-  font-size: 18px;
 }
 
 div #survey_widget fieldset select {
-  display:block;
 }
 
 div #survey_widget fieldset textarea,
--- a/app/soc/content/js/survey-edit-090703.js	Mon Jul 06 15:06:05 2009 +0200
+++ b/app/soc/content/js/survey-edit-090703.js	Mon Jul 06 16:13:27 2009 +0200
@@ -76,6 +76,18 @@
         }
       );
 
+      // add index information to choice fields
+      widget.find('[name=create-option-button]').each(
+        function () {
+          $(
+            '#index_for_' + $(this).attr('value')
+          )
+          .val(
+            $(this).getPosition()
+          );
+        }
+      );
+
       widget.find('.long_answer').each(
         function () {
           $(this).attr('name', SURVEY_PREFIX + $(this).getPosition() +
@@ -159,7 +171,7 @@
           }
         );
 
-        widget.find('.long_answer').each(
+        widget.find('.long_answer, .tooltip_entry').each(
           function () {
             if ($(this).val().length < 1 ||
             $(this).val() === DEFAULT_LONG_ANSWER_TEXT) {
@@ -350,7 +362,7 @@
         );
 
         // don't save default value
-        widget.find('.long_answer').each(
+        widget.find('.long_answer, .tooltip_entry').each(
           function () {
             if ($(this).val() === DEFAULT_LONG_ANSWER_TEXT) {
               $(this).val('');
@@ -397,9 +409,9 @@
 
     // get position of survey field
     getPosition: function () {
-      var this_row = $(this).parents('tr:first');
-      var this_table = this_row.parents('table:first');
-      var position = this_table.find('tr').index(this_row) + '__';
+      var this_fieldset = $(this).parents('fieldset:first');
+      var this_table = this_fieldset.parents('table:first');
+      var position = this_table.find('fieldset').index(this_fieldset) + '__';
       return position;
     }
   });
@@ -555,7 +567,7 @@
 
             var new_field = false;
             var type = button_id + "__";
-            var field_count = survey_table.find('tr').length;
+            var field_count = survey_table.find('fieldset').length;
             var new_field_count = field_count + 1 + '__';
 
             var MIN_ROWS = 10;
@@ -586,7 +598,7 @@
                           ].join("");
               break;
             case "long_answer":
-              field_count = survey_table.find('tr').length;
+              field_count = survey_table.find('fieldset').length;
               new_field_count = field_count + 1 + '__';
               new_field = ['<fieldset>\n', '<label for="required_for_',
                            field_name, '">Required</label>',
@@ -628,7 +640,7 @@
                 '"/>'
               ].join("");
 
-              field_count = survey_table.find('tr').length;
+              field_count = survey_table.find('fieldset').length;
               new_field_count = field_count + 1 + '__';
               var formatted_name = (SURVEY_PREFIX + new_field_count + type +
                                     field_name);
--- a/app/soc/templates/soc/survey/universal_choice_editor.html	Mon Jul 06 15:06:05 2009 +0200
+++ b/app/soc/templates/soc/survey/universal_choice_editor.html	Mon Jul 06 16:13:27 2009 +0200
@@ -1,5 +1,8 @@
 <fieldset>
 
+<table>
+ <tr>
+  <td>
 {# Drop-down setting whether question is required #}
   <label for="required_for_{{ name }}">Required</label>
   <select id="required_for_{{ name }}" name="required_for_{{ name }}">
@@ -7,8 +10,9 @@
     >True</option>
     <option value="False" {% if not is_required %} selected='selected' {% endif %}
     >False</option>
-  </select><br/>
-
+  </select>
+  </td>
+  <td>
 {# Drop-down setting whether question allows comments #}
   <label for="comment_for_{{ name }}">Allow Comments</label>
   <select id="comment_for_{{ name }}" name="comment_for_{{ name }}">
@@ -16,8 +20,11 @@
     >True</option>
     <option value="False" {% if not has_comment %} selected='selected' {% endif %}
     >False</option>
-  </select><br/>
-
+  </select>
+  </td>
+ </tr>
+ <tr>
+  <td>
 {# Question type drop-down #}
   <label for="type_for_{{ name }}">Question Type</label>
   <select id="type_for_{{ name }}" name="type_for_{{ name }}">
@@ -25,7 +32,8 @@
     <option value="pick_multi" {{ is_pick_multi }}>pick_multi</option>
     <option value="pick_quant" {{ is_pick_quant }}>pick_quant</option>
   </select>
-
+  </td>
+  <td>
 {# Render widget drop-down #}
   <label for="render_for_{{ name }}">Render as</label>
   <select id="render_for_{{ name }}" name="render_for_{{ name }}">
@@ -33,10 +41,23 @@
     <option value="checkboxes" {{ is_checkboxes|safe }}>checkboxes</option>
     <option value="radio_buttons" {{ is_radio_buttons }}>radio_buttons</option>
   </select>
+  </td>
+ </tr>
+ <tr>
+  <td  colspan='2'>
+    <textarea id="tip_for_{{ name }}" name="tip_for_{{ name }}"
+     cols='20' rows='1' class="tooltip_entry">{{ tooltip_content }}</textarea>
+  </td>
+ </tr>
+</table>
+
 
 {# Each choice field has a hidden input where its options' order is stored. #}
   <input type="hidden" id="order_for_{{ name }}" name="order_for_{{ name }}"
    value=""/>
+{# The index for choice questions is also kept in a hidden input. #}
+  <input type="hidden" id="index_for_{{ name }}"
+   name="index_for_{{ name }}" value=""/>
 
 {# Open the ordered list. #}
   <ol id="{{ name }}" class="sortable">
--- a/app/soc/views/helper/surveys.py	Mon Jul 06 15:06:05 2009 +0200
+++ b/app/soc/views/helper/surveys.py	Mon Jul 06 16:13:27 2009 +0200
@@ -186,7 +186,11 @@
       else:
         key_val = getattr(self.survey_record, key, None)
         if is_multi and isinstance(key_val, basestring):
+          # TODO(ajaksu): find out if we still need this safety net
           key_val = key_val.split(',')
+        elif not is_multi and isinstance(key_val, list):
+          # old pick_multi record for a question that is now single choice
+          key_val = key_val[0] if key_val else ''
 
       data[key] = key_val
 
@@ -207,7 +211,7 @@
     post_dict = post_dict or {}
     self.survey_fields = {}
     schema = SurveyContentSchema(self.survey_content.schema)
-    extra_attrs = {}
+    attrs = {}
 
     # figure out whether we want a read-only view
     read_only = self.read_only
@@ -217,8 +221,9 @@
       survey_entity = self.survey_logic.getSurveyForContent(survey_content)
       deadline = survey_entity.survey_end
       read_only = deadline and (datetime.datetime.now() > deadline)
-    else:
-      extra_attrs['disabled'] = 'disabled'
+
+    if read_only:
+      attrs['disabled'] = 'disabled'
 
     # add unordered fields to self.survey_fields
     for field in self.survey_content.dynamic_properties():
@@ -239,7 +244,12 @@
 
       # check if question is required, it's never required when editing
       required = schema.getRequired(field)
-      kwargs = dict(label=label, req=required)
+
+      tip = schema.getTip(field)
+      kwargs = dict(label=label, req=required, tip=tip)
+
+      # copy attrs
+      extra_attrs = attrs.copy()
 
       # add new field
       addField(field, value, extra_attrs, schema, **kwargs)
@@ -258,15 +268,13 @@
     survey_order = self.survey_content.getSurveyOrder()
 
     # first, insert dynamic survey fields
-    for position, property in survey_order.items():
-      position = position * 2
-      fields.insert(position, property, self.survey_fields[property])
+    for position, property in sorted(survey_order.items()):
+      fields.insert(len(fields) + 1, property, self.survey_fields[property])
 
       # add comment if field has one and this isn't an edit view
       property = COMMENT_PREFIX + property
       if property in self.survey_fields:
-        fields.insert(position - 1, property,
-                           self.survey_fields[property])
+        fields.insert(len(fields) + 1, property, self.survey_fields[property])
     return fields
 
   def addLongField(self, field, value, attrs, schema, req=True, label='',
@@ -429,7 +437,9 @@
       attrs: the attrs for the widget
       tip: tooltip text for this field
     """
+
     attrs['class'] = 'comment'
+    attrs['rows'] = '1'
     widget = widgets.Textarea(attrs=attrs)
     comment_field = CharField(help_text=tip, required=False,
         label='Add a Comment (optional)', widget=widget, initial=comment)
@@ -494,10 +504,10 @@
       if label is None:
         continue
 
-      tip = 'Please provide an answer to this question.'
+      tip = schema.getTip(field)
       kwargs = schema.getEditFieldArgs(field, value, tip, label)
 
-      kwargs['widget'] = schema.getEditWidget(field, extra_attrs)
+      kwargs['widget'] = schema.getEditWidget(field, extra_attrs, tip)
 
       # add new field
       self.survey_fields[field] = schema.getEditField(field)(**kwargs)
@@ -539,11 +549,21 @@
     """Fetch question type for field e.g. short_answer, pick_multi, etc.
 
     Args:
-      field: name of the field to get the type from
+      field: name of the field to get the type for
     """
 
     return self.schema[field]["type"]
 
+  def getTip(self, field):
+    """Fetch question help text, used for tooltips.
+
+    Args:
+      field: name of the field to get the tooltip for
+    """
+
+    return self.schema[field].get('tip', '')
+
+
   def getRequired(self, field):
     """Check whether survey question is required.
 
@@ -600,11 +620,11 @@
     if kind in CHOICE_TYPES:
       kwargs['choices'] = tuple([(val, val) for val in value])
     else:
-      kwargs['initial'] = value
+      kwargs['initial'] = tip
 
     return kwargs
 
-  def getEditWidget(self, field, attrs):
+  def getEditWidget(self, field, attrs, tip):
     """Get survey editing widget for questions.
     """
 
@@ -615,14 +635,15 @@
     if kind in CHOICE_TYPES:
       widget = UniversalChoiceEditor
       render = self.getRender(field)
-      args = kind, render, is_required, has_comment
+      args = kind, render, is_required, has_comment, tip
     else:
       args = is_required, has_comment
       if kind == 'long_answer':
-        attrs['class'] = "text_question"
         widget = LongTextarea
       elif kind == 'short_answer':
         widget = ShortTextInput
+      attrs = attrs.copy()
+      attrs['class'] = kind
 
     kwargs = dict(attrs=attrs)
 
@@ -636,10 +657,9 @@
       logging.error('field %s not found in schema %s' %
                     (field, str(self.schema)))
       return
-    elif 'question' in self.schema[field]:
+    else:
       label = self.schema[field].get('question') or field
-    else:
-      label = field
+
     return label
 
 
@@ -649,8 +669,8 @@
   Allows adding and removing options, re-ordering and editing option text.
   """
 
-  def __init__(self, kind, render, is_required, has_comment, attrs=None,
-               choices=()):
+  def __init__(self, kind, render, is_required, has_comment, tip,
+               attrs=None, choices=()):
     """
     params:
       kind: question kind (one of selection, pick_multi or pick_quant)
@@ -669,6 +689,7 @@
     self.render_as = render
     self.is_required = is_required
     self.has_comment = has_comment
+    self.tooltip_content = tip or ''
 
   def render(self, name, value, attrs=None, choices=()):
     """Render UCE widget.
@@ -708,6 +729,9 @@
       choices[i] = option_value
     context['choices'] = choices
 
+    tooltip_content = escape(forms.util.smart_unicode(self.tooltip_content))
+    context['tooltip_content'] = tooltip_content
+
     template = 'soc/survey/universal_choice_editor.html'
     return loader.render_to_string(template, context)
 
--- a/app/soc/views/models/survey.py	Mon Jul 06 15:06:05 2009 +0200
+++ b/app/soc/views/models/survey.py	Mon Jul 06 16:13:27 2009 +0200
@@ -348,7 +348,6 @@
           if 'NEW_' + name in POST:
             # new Choice question, set generic type and get its index
             schema[name] = {'type': 'choice'}
-            schema[name]['index'] = int(POST['index_for_' + name])
 
         if name in schema and schema[name]['type'] in CHOICE_TYPES:
           # build an index:content dictionary
@@ -358,7 +357,7 @@
           else:
             survey_fields[name] = {int(number): value}
 
-      elif key.startswith('survey__'): # new Text question
+      elif key.startswith('survey__'): # Text question
         # this is super ugly but unless data is serialized the regex is needed
         prefix = re.compile('survey__([0-9]{1,3})__')
         prefix_match = re.match(prefix, key)
@@ -373,10 +372,15 @@
           # should only match one
           if ptype + "__" in field_name:
             field_name = field_name.replace(ptype + "__", "")
-            schema[field_name] = {}
+            if field_name not in schema:
+              schema[field_name]= {}
             schema[field_name]["index"] = index
             schema[field_name]["type"] = ptype
 
+        # store text question tooltip from the input/textarea value
+        schema[field_name]["tip"] = value
+
+        # add the question as a dynamic property to survey_content
         survey_fields[field_name] = value
 
   def getSchemaOptions(self, schema, survey_fields, POST):
@@ -397,6 +401,10 @@
           schema[key]['render'] = RENDER[POST[render_for]]
           schema[key]['type'] = RENDER_TYPES[POST[render_for]]
 
+        # set the choice question's tooltip
+        tip_for = 'tip_for_' + key
+        schema[key]['tip'] = POST.get(tip_for)
+
         # handle reordering fields
         ordered = False
         order = 'order_for_' + key
@@ -422,7 +430,7 @@
 
       # set 'question' entry (free text label for question) in schema
       question_for = 'NEW_' + key
-      if question_for in POST:
+      if question_for in POST and POST[question_for]:
         schema[key]["question"] = POST[question_for]
 
       # set wheter the question is required
@@ -433,6 +441,11 @@
       comment_for = 'comment_for_' + key
       schema[key]['has_comment'] = BOOL[POST[comment_for]]
 
+      # set the question index from JS-calculated value
+      index_for = 'index_for_' + key
+      if index_for in POST:
+        schema[key]['index'] = int(POST[index_for].replace('__', ''))
+
   def createGet(self, request, context, params, seed):
     """Pass the question types for the survey creation template.
     """