Added ability to add custom tooltips and UI improvements.
Reviewed by: Lennard de Rijk
--- 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.
"""