Replaces the existing range drop-down widget with a html5 range input.

* For older browsers there's a javascript fallback which ensures that the values entered into the text input are fall within min-max and are divisable by step.
 * The new widget adds two new checks - step and unit.
 * The unit is displayed next to the input element.
EmailTemplateFixes
Denis Krienbühl 2012-01-16 16:06:43 +01:00
parent 9945ecae79
commit 1d2f5def85
5 changed files with 143 additions and 29 deletions

View File

@ -1,41 +1,83 @@
from questionnaire import *
from django.conf.urls.static import settings
from django.utils.translation import ugettext as _
from django.utils.simplejson import dumps
@question_proc('range')
def question_range(request, question):
cd = question.getcheckdict()
Range = cd.get('range', '1-5')
try:
rmin, rmax = Range.split('-', 1)
rmin, rmax = int(rmin), int(rmax)
except ValueError:
rmin = 0
rmax = int(range)
selected = int(request.POST.get('question_%s' % question.number, rmin))
Range = range(rmin, rmax+1)
rmin, rmax = parse_range(cd)
rstep = parse_step(cd)
runit = cd.get('unit', '')
current = request.POST.get('question_%s' % question.number, rmin)
return {
'required' : True,
'range' : Range,
'selected' : selected,
'rmin' : rmin,
'rmax' : rmax,
'rstep' : rstep,
'runit' : runit,
'current' : current,
'jsinclude' : [settings.STATIC_URL+'range.js']
}
@answer_proc('range')
def process_range(question, answer):
checkdict = question.getcheckdict()
cd = question.getcheckdict()
rmin, rmax = parse_range(cd)
rstep = parse_step(cd)
convert = range_type(rmin, rmax, rstep)
try:
rmin,rmax = checkdict.get('range','1-10').split('-',1)
rmin, rmax = int(rmin), int(rmax)
ans = convert(answer['ANSWER'])
except:
raise AnswerException("Error in question. Additional checks field should contain range='min-max'")
try:
ans = int(answer['ANSWER'])
except:
raise AnswerException("Could not convert `%r` to integer.")
if ans > rmax or ans < rmin:
raise AnswerException("Could not convert `%r`")
if ans > convert(rmax) or ans < convert(rmin):
raise AnswerException(_(u"Out of range"))
return dumps([ans])
add_type('range', 'Range of numbers [select]')
def parse_range(checkdict):
"Given a checkdict for a range widget return the min and max string values."
Range = checkdict.get('range', '1-5')
try:
rmin, rmax = Range.split('-', 1)
except ValueError:
rmin, rmax = '1', '5'
return rmin, rmax
def parse_step(checkdict):
"Given a checkdict for a range widget return the step as string value."
return checkdict.get('step', '1')
def range_type(rmin, rmax, step):
"""Given the min, max and step value return float or int depending on
the number of digits after 0.
"""
if any((digits(rmin), digits(rmax), digits(step))):
return float
else:
return int
def digits(number):
"Given a number as string return the number of digits after 0."
if '.' in number or ',' in number:
if '.' in number:
return len(number.split('.')[1])
else:
return len(number.split(',')[1])
else:
return 0

View File

@ -80,4 +80,20 @@ html, body {
background-color: #f5f5f5;
padding: 20px 20px 10px;
margin: -20px -20px 20px;
}
.rangeinput div.input {
display: inline;
float: left;
}
.rangeinput label {
text-align: left;
max-width: 40px;
margin-left: 10px;
padding-top: 4px;
}
.rangeinput {
margin-top: 5px;
}

View File

@ -0,0 +1,59 @@
(function($){$(document).ready(function() {
// true if the native html5 range input type is supported by the browser
var range_support = (function(){
var el=document.createElement("input");
el.setAttribute("type", "range");
return el.type=="range";
})();
// if range is not supported the input will be a simple text field
// in which case the following function ensures that the values
// in this text field are within the constraints of min, max, step
var normalize_value = function(input) {
var input = $(input);
var min = parseFloat(input.attr('min'));
var max = parseFloat(input.attr('max'));
var step = parseFloat(input.attr('step'));
var val = parseFloat(input.attr('value'));
if (val > max) val = max;
if (val < min) val = min;
// returns the number of digits after the decimal point
var digits = function(value){
var str = value.toString();
if (str.indexOf('.') == -1 && str.indexOf(',') == -1)
return 0;
if (str.indexOf('.') > -1)
return str.split('.')[1].length;
else
return str.split(',')[1].length;
};
// rounds the number to the next step
var round = function(val, step) {
return Math.round(val * (1 / step)) / (1 / step);
};
// round the number and fix the digits
return round(val, step).toFixed(digits(val));
};
if (range_support) {
$('.rangeinput input').change(function() {
var label = $(this).parent().parent().find('label');
var unit = label.attr('data-unit');
label.html($(this).val() + unit);
});
$('.rangeinput input').trigger('change');
} else {
$('.rangeinput input').change(function() {
$(this).val(normalize_value(this));
});
}
});})(jQuery);

View File

@ -2,8 +2,8 @@
{% load markup questionnaire i18n %}
{% block headextra %}
<script type="text/javascript" src="/static/questionset.js"></script>
<link rel="stylesheet" href="/static/progressbar.css"></script>
<script type="text/javascript" src="{{ STATIC_URL }}questionset.js"></script>
<link rel="stylesheet" href="{{ STATIC_URL }}progressbar.css"></script>
{% for x in jsinclude %}
<script type="text/javascript" src="{{ x }}"></script>

View File

@ -1,9 +1,6 @@
<div class="clearfix">
<div class="clearfix rangeinput">
<div class="input">
<select onchange="valchanged('{{ question.number }}', this.options[this.selectedIndex].value);" name="question_{{ question.number }}">
{% for x in qdict.range %}
<option value="{{ x }}" {% ifequal qdict.selected x %}selected{% endifequal %}>&nbsp;&nbsp;{{ x }}&nbsp;&nbsp;</option>
{% endfor %}
</select>
<input name="question_{{ question.number }}" type="range" min="{{ qdict.rmin }}" max="{{ qdict.rmax }}" step="{{ qdict.rstep }}" value="{{ qdict.current }}">
</div>
</div>
<label data-unit="{{ qdict.runit }}">{{ qdict.runit }}</label>
</div>