Commit 32c331b9 authored by stef's avatar stef
Browse files

[enh] autocompletion, and other ui wizzardry

parent 60484f07
from django import forms
from django.conf import settings
from django.forms import ModelForm
from bt.models import Violation
from bt.models import Violation, COUNTRIES
from operator import itemgetter
class AdvancedEditor(forms.Textarea):
class Media:
......@@ -13,9 +13,15 @@ class AdvancedEditor(forms.Textarea):
if attrs: self.attrs.update(attrs)
super(AdvancedEditor, self).__init__(attrs)
class AddViolation(ModelForm):
class Meta:
model = Violation
widgets = {
'comments': AdvancedEditor,
}
class AddViolation(forms.Form):
country = forms.ChoiceField(choices=(('',''),)+tuple(sorted(COUNTRIES,key=itemgetter(1))))
operator = forms.CharField(max_length=256)
contract = forms.CharField(max_length=256)
comment = forms.CharField(required=True, widget=AdvancedEditor())
resource = forms.CharField(required=False, max_length=1)
type = forms.CharField(max_length=1)
media = forms.CharField(required=False, max_length=1)
temporary = forms.BooleanField(required=False )
contractual = forms.BooleanField(required=False)
contract_excerpt = forms.CharField(required=False, widget=AdvancedEditor())
loophole = forms.BooleanField(required=False)
from django.db import models
# Create your models here.
COUNTRIES = (
('BE', 'Belgium'),
('BG', 'Bulgaria'),
('CZ', 'Czech Republic'),
('DK', 'Denmark'),
('DE', 'Germany'),
('EE', 'Estonia'),
('IE', 'Ireland'),
('EL', 'Greece'),
('ES', 'Spain'),
('FR', 'France'),
('IT', 'Italy'),
('CY', 'Cyprus'),
('LV', 'Latvia'),
('LT', 'Lithuania'),
('LU', 'Luxembourg'),
('HU', 'Hungary'),
('MT', 'Malta'),
('NL', 'Netherlands'),
('AT', 'Austria'),
('PL', 'Poland'),
('PT', 'Portugal'),
('RO', 'Romania'),
('SI', 'Slovenia'),
('SK', 'Slovakia'),
('FI', 'Finland'),
('SE', 'Sweden'),
('UK', 'United Kingdom '),
)
class Attachment(models.Model):
attachment = models.FileField(upload_to='static')
......@@ -12,22 +40,6 @@ class Comment(models.Model):
attachments = models.ForeignKey(Attachment)
class Violation(models.Model):
COUNTRIES = (
# TODO complete and sort
('A', 'Austria'),
('B', 'Belgium'),
('CZ', 'Czech Republic'),
('HU', 'Hungary'),
('RO', 'Romania'),
('SE', 'Sweden'),
('PL', 'Poland'),
('ES', 'Spain'),
('PT', 'Portugal'),
('I', 'Italy'),
('DE', 'Germany'),
('SK', 'Slovakia'),
('FR', 'France'),
)
RESOURCES = (
('1', 'port'),
('2', 'protocol'),
......@@ -44,33 +56,14 @@ class Violation(models.Model):
('1', 'Fixed'),
('2', 'Mobile'),
)
country = models.CharField(max_length=2,
choices=COUNTRIES,
help_text='',
)
operator = models.CharField(max_length=256,
help_text='')
contract = models.CharField(max_length=256,
help_text='type of offer, i.e. the name of the subscription')
comments = models.ForeignKey(Comment,
help_text='')
resource = models.CharField(blank=True,
max_length=1,
choices=RESOURCES,
help_text='')
type = models.CharField(blank=True,
max_length=1,
choices=RESOURCES,
help_text='')
media = models.CharField(blank=True,
max_length=1,
choices=MEDIA,
help_text='')
temporary = models.BooleanField(blank=True,
help_text='')
contractual = models.BooleanField(blank=True,
help_text='')
contract_excerpt = models.TextField(blank=True,
help_text='')
loophole = models.BooleanField(blank=True,
help_text='')
country = models.CharField(max_length=2, choices=COUNTRIES)
operator = models.CharField(max_length=256)
contract = models.CharField(max_length=256)
comments = models.ForeignKey(Comment)
resource = models.CharField(max_length=1, choices=RESOURCES)
type = models.CharField(max_length=1, choices=TYPES)
media = models.CharField( max_length=1, choices=MEDIA)
temporary = models.BooleanField( )
contractual = models.BooleanField()
contract_excerpt = models.TextField()
loophole = models.BooleanField()
# Create your views here.
from forms import AddViolation
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import render_to_response
from django.template import RequestContext
from models import Violation
import json
def add(request):
if request.method == 'POST': # If the form has been submitted...
form = ViolationForm(request.POST) # A form bound to the POST data
form = AddViolation(request.POST) # A form bound to the POST data
print form.errors
v=Violation(**form.cleaned_data)
print 'v',v
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
v=Violation(**form.values)
print 'v',v
return HttpResponseRedirect('/') # Redirect after POST
else:
form = AddViolation() # An unbound form
return render_to_response('add.html', {
'form': form,
})
}, context_instance=RequestContext(request))
def edit(request):
if request.method == 'POST': # If the form has been submitted...
form = ViolationForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/') # Redirect after POST
else:
form = AddViolation() # An unbound form
return add(request)
return render_to_response('add.html', {
'form': form,
})
def ajax(request, country=None, operator=None):
if not operator:
print 'c',sorted(list(set([x.operator for x in Violation.objects.filter(country=country)])))
return HttpResponse('["Vodafone", "T-Mobile", "T-Home", "UPC Chello", "Orange"]')
else:
print 'co', sorted(list(set([x.operator for x in Violation.objects.filter(country=country).filter(operator=operator)])))
return HttpResponse('["Basic", "Surfer", "Gamer", "Pro", "Business"]')
/*****************************************************************************
* jQuery autocomplete
****************************************************************************/
.ac_results {
padding: 0px;
border: 1px solid #ccc;
background-color: #fff;
overflow: hidden;
z-index: 99999;
text-align: left;
}
.ac_results ul {
width: 100%;
list-style-position: outside;
list-style: none;
padding: 0;
margin: 0;
}
.ac_results li {
margin: 0px;
padding: 3px 5px;
cursor: default;
display: block;
font: menu;
font-size: 12px;
line-height: 14px;
overflow: hidden;
}
.ac_loading {
background: white url('../img/indicator.gif') right center no-repeat;
}
.ac_odd {
background-color: #eee;
}
.ac_over {
background-color: #999;
color: white;
}
body { border: 0; padding: 0; font-family: Verdana,Arial,Helvetica,sans-serif; }
img { border: 0; }
h1, h2, h3, h4, div, ul { padding: 0; margin: 0; }
h2 { margin: auto; width: 50%; padding 4em; }
#addForm { margin: auto; width: 50%; padding 4em; }
.fieldWrapper { margin: 1em; }
.fieldWrapper label { width: 120px; display: inline-block; }
/*
* Autocomplete - jQuery plugin 1.0.2
*
* Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $
*
*/
;(function($) {
$.fn.extend({
autocomplete: function(urlOrData, options) {
var isUrl = typeof urlOrData == "string";
options = $.extend({}, $.Autocompleter.defaults, {
url: isUrl ? urlOrData : null,
data: isUrl ? null : urlOrData,
delay: isUrl ? $.Autocompleter.defaults.delay : 10,
max: options && !options.scroll ? 10 : 150
}, options);
// if highlight is set to false, replace it with a do-nothing function
options.highlight = options.highlight || function(value) { return value; };
// if the formatMatch option is not specified, then use formatItem for backwards compatibility
options.formatMatch = options.formatMatch || options.formatItem;
return this.each(function() {
new $.Autocompleter(this, options);
});
},
result: function(handler) {
return this.bind("result", handler);
},
search: function(handler) {
return this.trigger("search", [handler]);
},
flushCache: function() {
return this.trigger("flushCache");
},
setOptions: function(options){
return this.trigger("setOptions", [options]);
},
unautocomplete: function() {
return this.trigger("unautocomplete");
}
});
$.Autocompleter = function(input, options) {
var KEY = {
UP: 38,
DOWN: 40,
DEL: 46,
TAB: 9,
RETURN: 13,
ESC: 27,
COMMA: 188,
PAGEUP: 33,
PAGEDOWN: 34,
BACKSPACE: 8
};
// Create $ object for input element
var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
var timeout;
var previousValue = "";
var cache = $.Autocompleter.Cache(options);
var hasFocus = 0;
var lastKeyPressCode;
var config = {
mouseDownOnSelect: false
};
var select = $.Autocompleter.Select(options, input, selectCurrent, config);
var blockSubmit;
// prevent form submit in opera when selecting with return key
$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
if (blockSubmit) {
blockSubmit = false;
return false;
}
});
// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
// track last key pressed
lastKeyPressCode = event.keyCode;
switch(event.keyCode) {
case KEY.UP:
event.preventDefault();
if ( select.visible() ) {
select.prev();
} else {
onChange(0, true);
}
break;
case KEY.DOWN:
event.preventDefault();
if ( select.visible() ) {
select.next();
} else {
onChange(0, true);
}
break;
case KEY.PAGEUP:
event.preventDefault();
if ( select.visible() ) {
select.pageUp();
} else {
onChange(0, true);
}
break;
case KEY.PAGEDOWN:
event.preventDefault();
if ( select.visible() ) {
select.pageDown();
} else {
onChange(0, true);
}
break;
// matches also semicolon
case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
case KEY.TAB:
case KEY.RETURN:
if( selectCurrent() ) {
// stop default to prevent a form submit, Opera needs special handling
event.preventDefault();
blockSubmit = true;
return false;
}
break;
case KEY.ESC:
select.hide();
break;
default:
clearTimeout(timeout);
timeout = setTimeout(onChange, options.delay);
break;
}
}).focus(function(){
// track whether the field has focus, we shouldn't process any
// results if the field no longer has focus
hasFocus++;
}).blur(function() {
hasFocus = 0;
if (!config.mouseDownOnSelect) {
hideResults();
}
}).click(function() {
// show select when clicking in a focused field
if ( hasFocus++ > 1 && !select.visible() ) {
onChange(0, true);
}
}).bind("search", function() {
// TODO why not just specifying both arguments?
var fn = (arguments.length > 1) ? arguments[1] : null;
function findValueCallback(q, data) {
var result;
if( data && data.length ) {
for (var i=0; i < data.length; i++) {
if( data[i].result.toLowerCase() == q.toLowerCase() ) {
result = data[i];
break;
}
}
}
if( typeof fn == "function" ) fn(result);
else $input.trigger("result", result && [result.data, result.value]);
}
$.each(trimWords($input.val()), function(i, value) {
request(value, findValueCallback, findValueCallback);
});
}).bind("flushCache", function() {
cache.flush();
}).bind("setOptions", function() {
$.extend(options, arguments[1]);
// if we've updated the data, repopulate
if ( "data" in arguments[1] )
cache.populate();
}).bind("unautocomplete", function() {
select.unbind();
$input.unbind();
$(input.form).unbind(".autocomplete");
});
function selectCurrent() {
var selected = select.selected();
if( !selected )
return false;
var v = selected.result;
previousValue = v;
if ( options.multiple ) {
var words = trimWords($input.val());
if ( words.length > 1 ) {
v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
}
v += options.multipleSeparator;
}
$input.val(v);
hideResultsNow();
$input.trigger("result", [selected.data, selected.value]);
return true;
}
function onChange(crap, skipPrevCheck) {
if( lastKeyPressCode == KEY.DEL ) {
select.hide();
return;
}
var currentValue = $input.val();
if ( !skipPrevCheck && currentValue == previousValue )
return;
previousValue = currentValue;
currentValue = lastWord(currentValue);
if ( currentValue.length >= options.minChars) {
$input.addClass(options.loadingClass);
if (!options.matchCase)
currentValue = currentValue.toLowerCase();
request(currentValue, receiveData, hideResultsNow);
} else {
stopLoading();
select.hide();
}
};
function trimWords(value) {
if ( !value ) {
return [""];
}
var words = value.split( options.multipleSeparator );
var result = [];
$.each(words, function(i, value) {
if ( $.trim(value) )
result[i] = $.trim(value);
});
return result;
}
function lastWord(value) {
if ( !options.multiple )
return value;
var words = trimWords(value);
return words[words.length - 1];
}
// fills in the input box w/the first match (assumed to be the best match)
// q: the term entered
// sValue: the first matching result
function autoFill(q, sValue){
// autofill in the complete box w/the first match as long as the user hasn't entered in more data
// if the last user key pressed was backspace, don't autofill
if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
// fill in the value (keep the case the user has typed)
$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
// select the portion of the value not typed by the user (so the next character will erase)
$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
}
};
function hideResults() {
clearTimeout(timeout);
timeout = setTimeout(hideResultsNow, 200);
};
function hideResultsNow() {
var wasVisible = select.visible();
select.hide();
clearTimeout(timeout);
stopLoading();
if (options.mustMatch) {
// call search and run callback
$input.search(
function (result){
// if no value found, clear the input box
if( !result ) {
if (options.multiple) {
var words = trimWords($input.val()).slice(0, -1);
$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
}
else
$input.val( "" );
}
}
);
}
if (wasVisible)
// position cursor at end of input field
$.Autocompleter.Selection(input, input.value.length, input.value.length);
};
function receiveData(q, data) {
if ( data && data.length && hasFocus ) {
stopLoading();
select.display(data, q);
autoFill(q, data[0].value);
select.show();
} else {
hideResultsNow();
}
};
function request(term, success, failure) {
if (!options.matchCase)
term = term.toLowerCase();
var data = cache.load(term);
// recieve the cached data
if (data && data.length) {
success(term, data);
// if an AJAX url has been supplied, try loading the data now
} else if( (typeof options.url == "string") && (options.url.length > 0) ){
var extraParams = {
timestamp: +new Date()
};
$.each(options.extraParams, function(key, param) {
extraParams[key] = typeof param == "function" ? param() : param;
});
$.ajax({
// try to leverage ajaxQueue plugin to abort previous requests
mode: "abort",
// limit abortion to this input
port: "autocomplete" + input.name,
dataType: options.dataType,
url: options.url,
data: $.extend({
q: lastWord(term),
limit: options.max
}, extraParams),
success: function(data) {
var parsed = options.parse && options.parse(data) || parse(data);
cache.add(term, parsed);
success(term, parsed);
}
});
} else {
// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
select.emptyList();
failure(term);
}
};
function parse(data) {
var parsed = [];
var rows = data.spli