Commits

Jakub Zalewski committed 24b66ed

first!!!1

Comments (0)

Files changed (24)

+syntax: glob
+
+*.elc
+*.pyc
+*.db
+glob:django.wsgi
+
+syntax: regexp
+^__init__\.py$
+^manage\.py$
+^settings\.py$
+^urls\.py$
+^templates$
+^.settings$
+^\.project$
+^\.pydevproject$

inbox/__init__.py

Empty file added.
+#!/usr/bin/env python
+# encoding: utf-8
+from django.contrib import admin
+from inbox.models import Message
+
+class MessageAdmin(admin.ModelAdmin):
+    fields = ( 'user_to', 'user_from', 'subject', 'message', 'date_added', 'read', 're')
+    list_filter = ('date_added', 'user_to', 'user_from')
+    list_display = ('subject', 'user_from', 'user_to', 're', 'date_added',)
+    search_fields = ('subject', 'message',)
+    ordering = ('-date_added', '-id',)
+    raw_id_fields = ('user_to', 'user_from', )
+    list_per_page = 40
+    
+admin.site.register(Message, MessageAdmin)    
+    
+
+    
+    
+        
+     
+    

inbox/decorators.py

+#!/usr/bin/env python
+# encoding: utf-8
+from django.utils import simplejson
+from django.http import HttpResponse
+
+def ajax_login_required(view_func):
+    def wrap(request, *args, **kwargs):
+        if request.user.is_authenticated():
+            return view_func(request, *args, **kwargs)
+        json = simplejson.dumps({'not_authenticated': True})
+        return HttpResponse(json, mimetype='application/json')
+    wrap.__doc__ = view_func.__doc__
+    wrap.__dict__ = view_func.__dict__
+    return wrap
+#!/usr/bin/env python
+# encoding: utf-8
+from django import forms
+from django.forms import ModelForm
+from inbox.models import Message
+from django.contrib.auth.models import User
+from django.utils.translation import ugettext_lazy as _
+from django.forms.widgets import HiddenInput
+
+
+class MessageForm(ModelForm):
+    
+    username = forms.CharField(max_length=30, label=_('User'))
+    re = forms.ModelChoiceField(Message.objects.all(), widget=HiddenInput, required=False)
+
+    def __init__(self, *args, **kwargs):
+        disable_username = kwargs.pop('disable_username', False)
+        super(MessageForm, self).__init__(*args, **kwargs)            
+        if disable_username:
+            self.fields['username'].widget.attrs['readonly'] = True
+        
+    def save(self, user_from, user_to, *args, **kwargs):
+        self.instance.user_from = user_from
+        self.instance.user_to = user_to
+        #if self.instance.re > 0:    
+        #    try:
+        #        self.instance.re = Message.objects.get(id=self.instance.re, user_to=user_from, user_from=user_to)
+        #    except Message.DoesNotExist:
+        #        self.instance.re = None
+        mess = super(MessageForm, self).save(*args, **kwargs)
+        return mess
+    
+    class Meta:
+        model = Message
+        fields = ('username', 'subject', 'message', 're')
+        
+    def clean_username(self):
+        user_to = self.cleaned_data['username']
+        try:
+            user = User.objects.get(username=user_to)
+        except User.DoesNotExist:
+            raise forms.ValidationError(_('The user you entered does not exist.'))
+        return user_to
+#!/usr/bin/env python
+# encoding: utf-8
+from django.db import models
+from django.contrib.auth.models import User
+from datetime import datetime
+
+class Message(models.Model):
+    user_to = models.ForeignKey(User, verbose_name="To user", related_name="user_message_to")
+    user_from = models.ForeignKey(User, verbose_name="From user", related_name="user_message_from")
+    subject = models.CharField("Subject", max_length=40)
+    message = models.TextField("Text")
+    date_added = models.DateTimeField("Date added", default=datetime.now)    
+    read = models.BooleanField("Read?", default=False)
+    re = models.ForeignKey('self', related_name="re_message", verbose_name="Response to", null=True, blank=True)
+    deleted = models.BooleanField("Deleted?", default=False)
+    
+    class Meta:
+        verbose_name = "Message"
+        verbose_name_plural = "Messages"
+        ordering = ['-date_added', '-id']
+    def __unicode__(self):
+        return self.subject

inbox/templates/inbox/inbox.html

+{% extends "inbox/inbox_base.html" %}
+
+{% load ago %}
+{% load i18n %}
+
+{% block title %}{{ block.super }}{% endblock %}
+
+{% block content %}
+	{% if sent %}{% trans "message has been sent" %}{% endif %}
+	{% if message_list %}
+		<ul>
+		{% for m in message_list %}
+			<li>{% include "inbox/message_head.html" %}</li>
+		{% endfor %}
+		</ul>
+		{% include "includes/pagination.html" %}		
+	{% else %}
+		<p>{% trans "no new messages" %}</p>
+	{% endif %}
+{% endblock %}

inbox/templates/inbox/inbox_base.html

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title></title>
+<link rel="stylesheet" href="{{ MEDIA_URL }}css/jquery.autocomplete.css" />
+</head>
+<body>
+{% load i18n %}
+<h2>{% block title %}{% trans "inbox" %}{% endblock %}</h2>
+{% block menu %}
+	<ul id="InboxMenu">
+		<li><a href="{% url inbox %}">{% trans "inbox" %}</a></li>
+		<li><a href="{% url inbox_write %}">{% trans "write" %}</a></li>
+		<li><a href="{% url inbox_sent %}">{% trans "sent" %}</a></li>
+		<li><a href="{% url inbox_deleted %}">{% trans "deleted" %}</a></li>
+	</ul>
+{% endblock %}
+<hr />
+{% block content %}
+
+{% endblock %}
+<script type="text/javascript" src="http://www.google.com/jsapi"></script>
+<script type="text/javascript">google.load("jquery", "1.4.2");</script>
+<script type="text/javascript" src="{{ MEDIA_URL }}js/jquery.autocomplete.min.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}js/inbox.js"></script>
+</body>
+</html>

inbox/templates/inbox/inbox_counter.html

+{% if count > 0 %}<span>{{ count }}</span>{% endif %}

inbox/templates/inbox/message.html

+{% extends "inbox/inbox_base.html" %}
+
+{% load ago %}
+{% load i18n %}
+
+{% block title %}{{ m.re.subject }}{% endblock %}
+
+{% block content %}	
+			
+	{% with message as m %}
+	
+		{% include "inbox/message_head.html" %}
+		
+		{% if m.re %}		
+			<h6>{% trans "reply to" %} <a href="{% url inbox_message m.re.id%}">{{ m.re.subject }}</a> ({{ m.re.date_added|ago }})</h6>
+			<p>"{{ m.re.message|truncatewords:20 }}"</p>
+		{% endif %}
+		{{ m.message|linebreaks }}	
+	
+		{% if m.user_to == request.user %}		
+			<form method="post" action="{% url inbox_write_to m.user_from.username %}" id="InboxWrite">{% csrf_token %}
+				<table>
+				{{ form.as_table }}
+				<tr><td><label></label></td><td><input class="submit" type="submit" value="{% trans 'send' %}" /></td></tr>
+				</table>	
+			</form>
+		
+		{% endif %}
+	{% endwith %}
+
+{% endblock %}

inbox/templates/inbox/message_head.html

+{% load ago %}
+{% load i18n %}
+<h4>
+{% if m.user_to == request.user %}
+	<a href="">{{ m.user_from }}</a>
+{% else %}
+	<a href="">{{ m.user_to }}</a>
+{% endif %}
+<a href="{% url inbox_message m.id%}" {% if not m.read %}class="unread"{% endif %}>{{ m.subject|truncatewords:9 }}</a></h4>
+<h5>{{ m.date_added|ago }}</h5>

inbox/templates/inbox/outbox.html

+{% extends "inbox/inbox_base.html" %}
+
+{% load ago %}
+{% load i18n %}
+
+{% block title %}{% trans "sent messages" %}{% endblock %}
+
+{% block content %}
+	{% if sent %}Message has been sent{% endif %}
+	{% if message_list %}
+		<ul>
+		{% for m in message_list %}
+			<li>{% include "inbox/message_head.html" %}</li>
+		{% endfor %}
+		</ul>		
+		{% include "includes/pagination.html" %}
+	{% else %}
+		<p>{% trans "no new messages" %}</p>
+	{% endif %}
+
+{% endblock %}

inbox/templates/inbox/write.html

+{% extends "inbox/inbox_base.html" %}
+
+{% load ago %}
+{% load i18n %}
+
+{% block title %}
+	{% if user_to %}{% trans "write message to" %} {{ user_to }}{% else %}{% trans "write message" %}{% endif %}
+{% endblock %}
+
+{% block content %}
+	<form method="post" action="" id="InboxWrite">{% csrf_token %}	
+		<table>
+		{{ form.as_table }}
+		<tr><td><label></label></td><td><input class="submit" type="submit" value="{% trans 'send' %}" /></td></tr>
+		</table>	
+	</form>
+{% endblock %}

inbox/templates/includes/pagination.html

+{% load i18n %}
+<div id="paginator">
+	<ul>
+		{% if has_previous %}
+		    <li class="prev"><a href="?page={{ previous }}" title="{% trans 'previous' %}">&laquo; {% trans "previous" %}</a></li>
+		{% endif %}
+		<li><span class="current">{% blocktrans %}page {{ page }} of {{ pages }}{% endblocktrans %}</span></li>
+		{% if has_next %}
+			<li class="next"><a href="?page={{ next }}" title="{% trans 'next' %}">{% trans "next" %} &raquo;</a></li>
+		{% endif %}	
+	</ul>
+</div>

inbox/templatetags/__init__.py

Empty file added.

inbox/templatetags/ago.py

+#!/usr/bin/env python
+# encoding: utf-8
+from django import template
+from django.utils.timesince import timesince
+
+register = template.Library()
+
+@register.filter(name='ago')
+def ago(date):
+    ago = timesince(date)
+    return ago.split(",")[0] + " ago"

inbox/templatetags/inbox_counter.py

+#!/usr/bin/env python
+# encoding: utf-8
+from django import template
+register = template.Library()
+from inbox.utlis import unread_counter
+
+@register.inclusion_tag("inbox/inbox_counter.html")
+def inbox_counter(user):
+    return { 'user' : user, 'count' : unread_counter(user),}
+#!/usr/bin/env python
+# encoding: utf-8
+from django.test import TestCase, Client
+from django.contrib.auth.models import User
+from inbox.models import Message
+from django.core.urlresolvers import reverse
+   
+class TestInbox(TestCase):
+    def setUp(self):
+        self.client = Client()
+        user1,created = User.objects.get_or_create(username='test1')
+        user1.set_password('test1')
+        user1.save()
+        user2,created = User.objects.get_or_create(username='test2')
+        user2.set_password('test2')
+        user2.save()
+        user3,created = User.objects.get_or_create(username='test3')
+        user3.set_password('test3')
+        user3.save()                
+        self.user1 = user1
+        self.user2 = user2
+        self.user3 = user3                
+    
+    def log_in(self):
+        login = self.client.login(username='test1', password='test1')
+        self.failUnless(login, 'Could not log in')               
+    
+    def test_inbox_notloggedin(self):
+        response = self.client.get(reverse('inbox'))
+        self.assertRedirects(response, '%s?next=%s' % (reverse('auth_login'), reverse('inbox')))        
+        self.log_in()        
+        response = self.client.get(reverse('inbox'))
+        self.assertEqual(response.status_code, 200)
+               
+    def test_inbox_empty(self):           
+        self.log_in()        
+        response = self.client.get(reverse('inbox'))
+        self.assertEqual(response.status_code, 200)
+        self.assertTemplateUsed(response, 'inbox/inbox.html')
+        self.failUnless('no new messages' in response.content)
+    
+    def test_inbox(self):
+        self.log_in()        
+        response = self.client.get(reverse('inbox'))
+        self.assertEqual(response.status_code, 200)
+        self.assertTemplateUsed(response, 'inbox/inbox.html')        
+      
+    def test_message(self):
+        self.log_in()
+        
+        # prepare messages
+        message1 = Message.objects.create(user_from=self.user1, user_to=self.user2, subject='test', message='', read=False, deleted=False)
+        message2 = Message.objects.create(user_from=self.user2, user_to=self.user1, subject='test', message='', read=False, deleted=False)
+        message3 = Message.objects.create(user_from=self.user3, user_to=self.user2, subject='test', message='', read=False, deleted=False)
+        
+        # not existing                
+        response = self.client.get(reverse('inbox_message', args=[14543]))
+        self.assertEqual(response.status_code, 404)
+        # existing, where i am not recipient or author
+        response = self.client.get(reverse('inbox_message', args=[3]))
+        self.assertEqual(response.status_code, 404)
+        # proper message to me unread
+        response = self.client.get(reverse('inbox_message', args=[message2.id]))
+        self.assertEqual(response.status_code, 200)
+        # is it now marked as read?
+        message = Message.objects.get(id=message2.id)
+        self.assertEqual(message.read, True)
+    
+    def test_write(self):
+        self.log_in()
+        
+        # bad user
+        post = {'username':'sdsdsasdasdasd', 'subject':'subject', 'message':'message', }
+        response = self.client.post(reverse('inbox_write'), post)
+        self.assertEqual(response.status_code, 200)
+        self.assertTemplateUsed(response, 'inbox/write.html')
+        self.assertFormError(response, 'form', 'username', 'The user you entered does not exist.')
+        
+        # bad post
+        post = {'username':'test1', }
+        response = self.client.post(reverse('inbox_write'), post)
+        self.assertEqual(response.status_code, 200)
+        self.assertTemplateUsed(response, 'inbox/write.html')   
+        self.failUnless('errorlist' in response.content)
+        
+        # good post
+        post = {'username':'test1', 'user_from':1, 'subject':'subject', 'message':'message', }
+        response = self.client.post(reverse('inbox_write'), post)
+        self.assertRedirects(response, '%s?sent=1' % reverse('inbox_sent'))
+    
+    def test_delete(self):
+        self.log_in()
+        
+        # prepare message to me
+        message_received = Message.objects.create(user_from=self.user2, user_to=self.user1, subject='test', message='blah blah', read=False, deleted=False)
+        message_sent = Message.objects.create(user_from=self.user1, user_to=self.user2, subject='test', message='blah blah', read=False, deleted=False)
+        message_other = Message.objects.create(user_from=self.user2, user_to=self.user3, subject='test', message='blah blah', read=False, deleted=False)
+        
+        # try to delete other's message
+        response = self.client.get(reverse('inbox_delete', args=[message_other.id]))
+        self.assertEqual(response.status_code, 404)
+        
+        # try to delete sent message
+        response = self.client.get(reverse('inbox_delete', args=[message_sent.id]))
+        self.assertEqual(response.status_code, 404)        
+        
+        # delete own message
+        response = self.client.get(reverse('inbox_delete', args=[message_received.id]))
+        self.assertRedirects(response, '%s?deleted=1' % reverse('inbox'))
+        
+        # see if author has it in 'sent' - he should, it deletes only from recipient inbox
+        logout = self.client.logout()
+        login = self.client.login(username='test2', password='test2')
+        self.failUnless(login, 'Could not log in')  
+        response = self.client.get(reverse('inbox_message', args=[message_sent.id]))
+        self.assertEqual(response.status_code, 200)
+    
+    def test_respond(self):
+        self.log_in()
+        
+        
+        
+   
+
+#!/usr/bin/env python
+# encoding: utf-8
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+    url(r'^message/(?P<id>\d+)/$', 'inbox.views.message', name="inbox_message"),    
+    url(r'^delete/(?P<id>\d+)/$', 'inbox.views.delete', name="inbox_delete"),    
+    url(r'^deleted/$', 'inbox.views.inbox_deleted', name="inbox_deleted"),    
+    url(r'^sent/$', 'inbox.views.outbox', name="inbox_sent"),
+    url(r'^write_to/(?P<username>\w+)/$', 'inbox.views.write_to', name="inbox_write_to"),    
+    url(r'^write/$', 'inbox.views.write', name="inbox_write"),
+    url(r'^autocomplete/$', 'inbox.views.ajax_autocomplete', name="inbox_autocomplete"),
+    url(r'^$', 'inbox.views.inbox', name="inbox"),
+)
+#!/usr/bin/env python
+# encoding: utf-8
+from django.db import models
+from django.contrib.auth.models import User
+from datetime import datetime
+from inbox.models import Message
+
+def send_message(user_from, user_to, subject, message, re=None):
+    message = Message(user_from=user_from, user_to=user_to, subject=subject[:40], message=message, re=re)
+    message.save()
+    return message
+
+def unread_counter(user):
+    return Message.objects.filter(user_to=user, read=False).count()
+#!/usr/bin/env python
+# encoding: utf-8
+from django.http import HttpResponse, HttpResponseRedirect, Http404
+from django.template import RequestContext
+from django.contrib.auth.models import User
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import render_to_response, get_object_or_404
+from django.core.urlresolvers import reverse
+from django.views.generic.list_detail import object_list
+from django.utils.translation import ugettext
+from django.conf import settings
+
+from inbox.forms import MessageForm
+from inbox.models import Message
+from inbox.decorators import ajax_login_required
+
+
+
+@login_required(redirect_field_name='next')
+def inbox(request):
+    """
+    Inbox main view - list of received messages
+    """
+    messages = Message.objects.filter(user_to=request.user, deleted=False)
+    template = 'inbox/inbox.html'
+    return object_list(request, 
+        queryset = messages,
+        paginate_by = 10, 
+        extra_context = { },
+        template_object_name = 'message',
+        template_name = template
+        )
+
+@login_required(redirect_field_name='next')
+def inbox_new(request):
+    """
+    List of only unread messages
+    """
+    messages = Message.objects.filter(user_to=request.user, deleted=False, read=False)
+    template = 'inbox/inbox.html'
+    return object_list(request, 
+        queryset = messages,
+        paginate_by = 10, 
+        extra_context = { },
+        template_object_name = 'message',
+        template_name = template
+        )
+    
+@login_required(redirect_field_name='next')
+def outbox(request):
+    """
+    List of sent messages
+    """
+    messages = Message.objects.filter(user_from=request.user)
+    template = 'inbox/outbox.html'
+    return object_list(request, 
+        queryset = messages,
+        paginate_by = 10, 
+        extra_context = { 'messages': messages },
+        template_object_name = 'message',
+        template_name = template
+        )
+    
+    
+@login_required(redirect_field_name='next')
+def inbox_deleted(request):
+    """
+    List of deleted messages
+    """
+    messages = Message.objects.filter(user_to=request.user, deleted=True)
+    template = 'inbox/inbox.html'
+    return object_list(request, 
+        queryset = messages,
+        paginate_by = 10, 
+        extra_context = { },
+        template_object_name = 'message',
+        template_name = template
+        )
+    
+
+@login_required(redirect_field_name='next')
+def write(request):
+    """
+    Write a new message
+    """
+    user_from = request.user
+    template = 'inbox/write.html'
+    if request.method=='POST':                
+        form = MessageForm(request.POST)
+        if form.is_valid():
+            user_to = User.objects.get(username=form.cleaned_data['username'])
+            message = form.save(user_from=user_from, user_to=user_to)
+            next = reverse('inbox_sent')+'?sent=1'
+            return HttpResponseRedirect(next)
+    else:
+        form = MessageForm()        
+    return render_to_response(template, {'form':form}, 
+                              context_instance=RequestContext(request))
+
+
+@login_required(redirect_field_name='next')
+def write_to(request, username):
+    """
+    Write a new message
+    """
+    user_to = get_object_or_404(User, username=username)
+    user_from = request.user
+    template = 'inbox/write.html'
+    if request.method=='POST':                
+        form = MessageForm(request.POST)
+        if form.is_valid():                    
+            message = form.save(user_from=user_from, user_to=user_to)
+            next = reverse('inbox_sent')+'?sent=1'
+            return HttpResponseRedirect(next)
+    else:
+        form = MessageForm(initial={'username': username,}, disable_username=True)        
+    return render_to_response(template, {'form':form, 'user_to': user_to}, 
+                              context_instance=RequestContext(request))
+            
+                        
+@login_required(redirect_field_name='next')
+def message(request, id):
+    """
+    A single message
+    """
+    template = 'inbox/message.html'
+    message = get_object_or_404(Message, id=id)
+    if message.user_to !=request.user and message.user_from !=request.user:
+        raise Http404
+    else:
+        if not message.read and message.user_to==request.user:
+            message.read = True
+            message.save()
+    re_word = ugettext('re: ')
+    subject = "%s%s" % (re_word, message.subject.replace(re_word, '')[:40])
+    #subject = message.subject
+    form = MessageForm(initial={'subject':subject, 're': id, 'username': message.user_from, }, disable_username=True)
+    return render_to_response(template, {'message':message, 'form':form,}, 
+                              context_instance=RequestContext(request))
+    
+    
+@login_required(redirect_field_name='next')
+def delete(request, id):
+    """
+    Delete a message. Actually it does only mark the message as deleted by the recipient, because the author can still see it in his sent messages.
+    """
+    message = get_object_or_404(Message, id=id, user_to=request.user)
+    message.deleted = True
+    message.save()
+    next = reverse('inbox')+'?deleted=1'
+    return HttpResponseRedirect(next)
+
+@ajax_login_required
+def ajax_autocomplete(request):
+    """
+    Results for autocomplete username in inbox form
+    """
+    q = request.GET.get('q')
+    try:
+        min_len = getattr(settings, 'INBOX_AUTOCOMPLETE_CHARS')
+    except AttributeError:
+        min_len = 2
+    data = []
+    if q and len(q)>min_len:
+        users = User.objects.filter(username__startswith=q).order_by('username')
+        [data.append(user.username) for user in users]
+    return HttpResponse('\n'.join(data))
+    
+    

static/css/jquery.autocomplete.css

+.ac_results {
+	padding: 0px;
+	overflow: hidden;
+	z-index: 99999;
+}
+
+.ac_results ul {
+	width: 100%;
+	list-style-position: outside;
+	list-style: none;
+	padding: 0;
+	margin: 0;
+background-color: #52BBD0;
+}
+
+.ac_results li {
+
+	margin: 0px;
+	padding: 2px 5px;
+	cursor: default;
+	display: block;
+	/* 
+	if width will be 100% horizontal scrollbar will apear 
+	when scroll mode will be used
+	*/
+	/*width: 100%;*/
+	font: menu;
+	font-size: 11px;
+	/* 
+	it is very important, if line-height not setted or setted 
+	in relative units scroll will be broken in firefox
+	*/
+	line-height: 13px;
+	overflow: hidden;
+
+}
+
+.ac_loading {
+/*
+	background: white url('indicator.gif') right center no-repeat;
+*/
+}
+
+.ac_odd {
+	background-color: #52BBD0;
+}
+
+.ac_over {
+	background-color: #DEEFFD;
+}

static/js/inbox.js

+$(document).ready(function(){
+	$("#InboxWrite #id_username").autocomplete('/inbox/autocomplete/');
+});

static/js/jquery.autocomplete.min.js

+/*
+ * jQuery Autocomplete plugin 1.1
+ *
+ * Copyright (c) 2009 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 15 2009-08-22 10:30:27Z 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);options.highlight=options.highlight||function(value){return value;};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};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;$.browser.opera&&$(input.form).bind("submit.autocomplete",function(){if(blockSubmit){blockSubmit=false;return false;}});$input.bind(($.browser.opera?"keypress":"keydown")+".autocomplete",function(event){hasFocus=1;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;case options.multiple&&$.trim(options.multipleSeparator)==","&&KEY.COMMA:case KEY.TAB:case KEY.RETURN:if(selectCurrent()){event.preventDefault();blockSubmit=true;return false;}break;case KEY.ESC:select.hide();break;default:clearTimeout(timeout);timeout=setTimeout(onChange,options.delay);break;}}).focus(function(){hasFocus++;}).blur(function(){hasFocus=0;if(!config.mouseDownOnSelect){hideResults();}}).click(function(){if(hasFocus++>1&&!select.visible()){onChange(0,true);}}).bind("search",function(){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("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){var seperator=options.multipleSeparator.length;var cursorAt=$(input).selection().start;var wordAt,progress=0;$.each(words,function(i,word){progress+=word.length;if(cursorAt<=progress){wordAt=i;return false;}progress+=seperator;});words[wordAt]=v;v=words.join(options.multipleSeparator);}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[""];if(!options.multiple)return[$.trim(value)];return $.map(value.split(options.multipleSeparator),function(word){return $.trim(value).length?$.trim(word):null;});}function lastWord(value){if(!options.multiple)return value;var words=trimWords(value);if(words.length==1)return words[0];var cursorAt=$(input).selection().start;if(cursorAt==value.length){words=trimWords(value)}else{words=trimWords(value.replace(value.substring(cursorAt),""));}return words[words.length-1];}function autoFill(q,sValue){if(options.autoFill&&(lastWord($input.val()).toLowerCase()==q.toLowerCase())&&lastKeyPressCode!=KEY.BACKSPACE){$input.val($input.val()+sValue.substring(lastWord(previousValue).length));$(input).selection(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){$input.search(function(result){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("");$input.trigger("result",null);}}});}};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);if(data&&data.length){success(term,data);}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({mode:"abort",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{select.emptyList();failure(term);}};function parse(data){var parsed=[];var rows=data.split("\n");for(var i=0;i<rows.length;i++){var row=$.trim(rows[i]);if(row){row=row.split("|");parsed[parsed.length]={data:row,value:row[0],result:options.formatResult&&options.formatResult(row,row[0])||row[0]};}}return parsed;};function stopLoading(){$input.removeClass(options.loadingClass);};};$.Autocompleter.defaults={inputClass:"ac_input",resultsClass:"ac_results",loadingClass:"ac_loading",minChars:1,delay:400,matchCase:false,matchSubset:true,matchContains:false,cacheLength:10,max:100,mustMatch:false,extraParams:{},selectFirst:true,formatItem:function(row){return row[0];},formatMatch:null,autoFill:false,width:0,multiple:false,multipleSeparator:", ",highlight:function(value,term){return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)("+term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"<strong>$1</strong>");},scroll:true,scrollHeight:180};$.Autocompleter.Cache=function(options){var data={};var length=0;function matchSubset(s,sub){if(!options.matchCase)s=s.toLowerCase();var i=s.indexOf(sub);if(options.matchContains=="word"){i=s.toLowerCase().search("\\b"+sub.toLowerCase());}if(i==-1)return false;return i==0||options.matchContains;};function add(q,value){if(length>options.cacheLength){flush();}if(!data[q]){length++;}data[q]=value;}function populate(){if(!options.data)return false;var stMatchSets={},nullData=0;if(!options.url)options.cacheLength=1;stMatchSets[""]=[];for(var i=0,ol=options.data.length;i<ol;i++){var rawValue=options.data[i];rawValue=(typeof rawValue=="string")?[rawValue]:rawValue;var value=options.formatMatch(rawValue,i+1,options.data.length);if(value===false)continue;var firstChar=value.charAt(0).toLowerCase();if(!stMatchSets[firstChar])stMatchSets[firstChar]=[];var row={value:value,data:rawValue,result:options.formatResult&&options.formatResult(rawValue)||value};stMatchSets[firstChar].push(row);if(nullData++<options.max){stMatchSets[""].push(row);}};$.each(stMatchSets,function(i,value){options.cacheLength++;add(i,value);});}setTimeout(populate,25);function flush(){data={};length=0;}return{flush:flush,add:add,populate:populate,load:function(q){if(!options.cacheLength||!length)return null;if(!options.url&&options.matchContains){var csub=[];for(var k in data){if(k.length>0){var c=data[k];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub.push(x);}});}}return csub;}else
+if(data[q]){return data[q];}else
+if(options.matchSubset){for(var i=q.length-1;i>=options.minChars;i--){var c=data[q.substr(0,i)];if(c){var csub=[];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub[csub.length]=x;}});return csub;}}}return null;}};};$.Autocompleter.Select=function(options,input,select,config){var CLASSES={ACTIVE:"ac_over"};var listItems,active=-1,data,term="",needsInit=true,element,list;function init(){if(!needsInit)return;element=$("<div/>").hide().addClass(options.resultsClass).css("position","absolute").appendTo(document.body);list=$("<ul/>").appendTo(element).mouseover(function(event){if(target(event).nodeName&&target(event).nodeName.toUpperCase()=='LI'){active=$("li",list).removeClass(CLASSES.ACTIVE).index(target(event));$(target(event)).addClass(CLASSES.ACTIVE);}}).click(function(event){$(target(event)).addClass(CLASSES.ACTIVE);select();input.focus();return false;}).mousedown(function(){config.mouseDownOnSelect=true;}).mouseup(function(){config.mouseDownOnSelect=false;});if(options.width>0)element.css("width",options.width);needsInit=false;}function target(event){var element=event.target;while(element&&element.tagName!="LI")element=element.parentNode;if(!element)return[];return element;}function moveSelect(step){listItems.slice(active,active+1).removeClass(CLASSES.ACTIVE);movePosition(step);var activeItem=listItems.slice(active,active+1).addClass(CLASSES.ACTIVE);if(options.scroll){var offset=0;listItems.slice(0,active).each(function(){offset+=this.offsetHeight;});if((offset+activeItem[0].offsetHeight-list.scrollTop())>list[0].clientHeight){list.scrollTop(offset+activeItem[0].offsetHeight-list.innerHeight());}else if(offset<list.scrollTop()){list.scrollTop(offset);}}};function movePosition(step){active+=step;if(active<0){active=listItems.size()-1;}else if(active>=listItems.size()){active=0;}}function limitNumberOfItems(available){return options.max&&options.max<available?options.max:available;}function fillList(){list.empty();var max=limitNumberOfItems(data.length);for(var i=0;i<max;i++){if(!data[i])continue;var formatted=options.formatItem(data[i].data,i+1,max,data[i].value,term);if(formatted===false)continue;var li=$("<li/>").html(options.highlight(formatted,term)).addClass(i%2==0?"ac_even":"ac_odd").appendTo(list)[0];$.data(li,"ac_data",data[i]);}listItems=list.find("li");if(options.selectFirst){listItems.slice(0,1).addClass(CLASSES.ACTIVE);active=0;}if($.fn.bgiframe)list.bgiframe();}return{display:function(d,q){init();data=d;term=q;fillList();},next:function(){moveSelect(1);},prev:function(){moveSelect(-1);},pageUp:function(){if(active!=0&&active-8<0){moveSelect(-active);}else{moveSelect(-8);}},pageDown:function(){if(active!=listItems.size()-1&&active+8>listItems.size()){moveSelect(listItems.size()-1-active);}else{moveSelect(8);}},hide:function(){element&&element.hide();listItems&&listItems.removeClass(CLASSES.ACTIVE);active=-1;},visible:function(){return element&&element.is(":visible");},current:function(){return this.visible()&&(listItems.filter("."+CLASSES.ACTIVE)[0]||options.selectFirst&&listItems[0]);},show:function(){var offset=$(input).offset();element.css({width:typeof options.width=="string"||options.width>0?options.width:$(input).width(),top:offset.top+input.offsetHeight,left:offset.left}).show();if(options.scroll){list.scrollTop(0);list.css({maxHeight:options.scrollHeight,overflow:'auto'});if($.browser.msie&&typeof document.body.style.maxHeight==="undefined"){var listHeight=0;listItems.each(function(){listHeight+=this.offsetHeight;});var scrollbarsVisible=listHeight>options.scrollHeight;list.css('height',scrollbarsVisible?options.scrollHeight:listHeight);if(!scrollbarsVisible){listItems.width(list.width()-parseInt(listItems.css("padding-left"))-parseInt(listItems.css("padding-right")));}}}},selected:function(){var selected=listItems&&listItems.filter("."+CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);return selected&&selected.length&&$.data(selected[0],"ac_data");},emptyList:function(){list&&list.empty();},unbind:function(){element&&element.remove();}};};$.fn.selection=function(start,end){if(start!==undefined){return this.each(function(){if(this.createTextRange){var selRange=this.createTextRange();if(end===undefined||start==end){selRange.move("character",start);selRange.select();}else{selRange.collapse(true);selRange.moveStart("character",start);selRange.moveEnd("character",end);selRange.select();}}else if(this.setSelectionRange){this.setSelectionRange(start,end);}else if(this.selectionStart){this.selectionStart=start;this.selectionEnd=end;}});}var field=this[0];if(field.createTextRange){var range=document.selection.createRange(),orig=field.value,teststring="<->",textLength=range.text.length;range.text=teststring;var caretAt=field.value.indexOf(teststring);field.value=orig;this.selection(caretAt,caretAt+textLength);return{start:caretAt,end:caretAt+textLength}}else if(field.selectionStart!==undefined){return{start:field.selectionStart,end:field.selectionEnd}}};})(jQuery);