Commits

Reed O'Brien committed 1f0c13d

Add autocomplete widget

  • Participants
  • Parent commits 23a247c

Comments (0)

Files changed (15)

+deform/static/scripts/jquery-autocomplete/changelog.txt
+deform/static/scripts/jquery-autocomplete/demo/bg.gif
+deform/static/scripts/jquery-autocomplete/demo/emails.php
+deform/static/scripts/jquery-autocomplete/demo/emails.phps
+deform/static/scripts/jquery-autocomplete/demo/images.php
+deform/static/scripts/jquery-autocomplete/demo/images/Amsterdam Van-Gogh Museum.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Amsterdam.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Antwerpen Rubenshaus.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Antwerpen.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Appenzell.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Arnhem Historisches Museum.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Bled.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Bled_Die Burg von Bled.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Bogojina_Die Pfarrkirche.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/BolognaBasilicadiSanPetronio.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/BolognaFontanadelNettuno.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/BolognaPiazzaMaggiore.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Bolsward Martinikerk.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Bolsward Stadhuis.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Bolsward.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/BordeauxND.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/BordeauxPlaceB.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/BotanischerGartenZuerich.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Bouillon.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Gent Hotel de Ville2.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Gent.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/GenuaStrand.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/GenuabeiNacht.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Giessbachfaelle Brienz.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Giethoorn.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Gnesen.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Gornij Grad_KATHEDRALE.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Gossensass.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Grad_Burg Grad2.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/GrandDixence.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/GrenoblePanorama.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Groningen.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/GrottenvonReclere.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Guebwiller.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Kamnik_Die Franziskaner Bibliothek.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Karlsbad Muehlbrunnkolonnade.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Kazimierz.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/KirchbergAltesRathaus1.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/KlagenfurtDom.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/KleineMeerjungfreu.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/LazienkiparkWarschau.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/LeHavreHafen.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/LeMans.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Lednice.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Leeuwarden Fries Museum.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Leeuwarden.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Lelystad.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Lemmer.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Leper Halles aux draps.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Leuven Museum fuer Kirchenkunst.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Leuven.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Luxemburg.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/LuzernAltstadt.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/LuzernPicassoMuseum.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Lyon.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Maastricht Onze Lieve Vrou...jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Maastricht St Servaasbasiliek.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Maastricht Walmuur.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Maastricht.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/MagiatalMaggia.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Mailand3.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Metlika_Bela Krajina Museum.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/MilanoCastelloSforzesco.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/MilanoDom.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/MilazzoBurg.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Novo Mesto_Das Museum.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/ObervellachBurgFalkenstein.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/OdenseeAndersen.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Olimje_Kirche und Apotheke in Olimje.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Olomouc.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/OlympischesMuseumLausanne.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/OrleansMaisonJeannedArc.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/OrleansParcFloraldelaSource.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/OstiaAntica.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Ostrow Tumski.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/PoertschachSchlossLeonstain.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Portoroz.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Posen.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Postojna.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Prag Altstaedter Ring.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Prag Waldsteinpalais.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/RouenNotreDame.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/Salzbergwerk Bex.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/SalzbergwerkWieliczka.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/SalzburgFestungHohensalzburg.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/SalzburgResidenz.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/WienMuseumsQuartier.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/WienMusikverein.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/WienRiesenrad.jpg
+deform/static/scripts/jquery-autocomplete/demo/images/WienRingstrasse.jpg
+deform/static/scripts/jquery-autocomplete/demo/index.html
+deform/static/scripts/jquery-autocomplete/demo/indicator.gif
+deform/static/scripts/jquery-autocomplete/demo/json.html
+deform/static/scripts/jquery-autocomplete/demo/localdata.js
+deform/static/scripts/jquery-autocomplete/demo/main.css
+deform/static/scripts/jquery-autocomplete/demo/search.php
+deform/static/scripts/jquery-autocomplete/demo/search.phps
+deform/static/scripts/jquery-autocomplete/jquery.autocomplete.css
+deform/static/scripts/jquery-autocomplete/jquery.autocomplete.js
+deform/static/scripts/jquery-autocomplete/jquery.autocomplete.pack.js
+deform/static/scripts/jquery-autocomplete/lib/jquery.ajaxQueue.js
+deform/static/scripts/jquery-autocomplete/lib/jquery.bgiframe.min.js
+deform/static/scripts/jquery-autocomplete/lib/jquery.js
+deform/static/scripts/jquery-autocomplete/lib/thickbox-compressed.js
+deform/static/scripts/jquery-autocomplete/lib/thickbox.css
+deform/static/scripts/jquery-autocomplete/todo

File deform/static/css/jquery.autocomplete.css

+.ac_results {
+	padding: 0px;
+	border: 1px solid black;
+	background-color: white;
+	overflow: hidden;
+	z-index: 99999;
+}
+
+.ac_results ul {
+	width: 100%;
+	list-style-position: outside;
+	list-style: none;
+	padding: 0;
+	margin: 0;
+}
+
+.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: 12px;
+	/* 
+	it is very important, if line-height not setted or setted 
+	in relative units scroll will be broken in firefox
+	*/
+	line-height: 16px;
+	overflow: hidden;
+}
+
+.ac_loading {
+	background: white url('indicator.gif') right center no-repeat;
+}
+
+.ac_odd {
+	background-color: #eee;
+}
+
+.ac_over {
+	background-color: #0A246A;
+	color: white;
+}

File deform/static/scripts/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);

File deform/templates/autocomplete_input.pt

+<span tal:omit-tag="">
+    <input type="text" name="${field.name}" value="${cstruct}" 
+           tal:attributes="size field.widget.size"
+           id="${field.oid}"/>
+    <script tal:condition="field.widget.values" type="text/javascript">
+      jQuery(function($) {
+          $("#${field.oid}").autocomplete(${values}, ${options});
+      });
+    </script>
+</span>

File deform/templates/readonly/autocomplete_input.pt

+<span tal:omit-tag="">
+    <input type="text" name="${field.name}" value="${cstruct}" 
+           tal:attributes="size field.widget.size"
+           id="${field.oid}"/>
+    <script tal:condition="field.widget.values" type="text/javascript">
+      jQuery(function($) {
+          $("#${field.oid}").autocomplete("${field.widget.mask}");
+      });
+    </script>
+</span>

File deform/tests/test_widget.py

         result = widget.deserialize(field, pstruct)
         self.assertEqual(result, null)
 
+class TestAutocompleteInputWidget(unittest.TestCase):
+    def _makeOne(self, **kw):
+        from deform.widget import AutocompleteInputWidget
+        return AutocompleteInputWidget(**kw)
+
+    def test_serialize_null(self):
+        from colander import null
+        widget = self._makeOne()
+        renderer = DummyRenderer()
+        field = DummyField(None, renderer=renderer)
+        widget.serialize(field, null)
+        self.assertEqual(renderer.template, widget.template)
+        self.assertEqual(renderer.kw['field'], field)
+        self.assertEqual(renderer.kw['cstruct'], '')
+
+    def test_serialize_None(self):
+        widget = self._makeOne()
+        renderer = DummyRenderer()
+        field = DummyField(None, renderer=renderer)
+        widget.serialize(field, None)
+        self.assertEqual(renderer.template, widget.template)
+        self.assertEqual(renderer.kw['field'], field)
+        self.assertEqual(renderer.kw['cstruct'], '')
+
+    def test_serialize_url(self):
+        try:
+            import json
+        except ImportError: #PRAGMA: nocover
+            import simplejson as json
+        widget = self._makeOne()
+        url='http://example.com'
+        widget.values = url
+        renderer = DummyRenderer()
+        schema = DummySchema()
+        field = DummyField(schema, renderer=renderer)
+        cstruct = 'abc'
+        widget.serialize(field, cstruct)
+        self.assertEqual(renderer.template, widget.template)
+        self.assertEqual(renderer.kw['field'], field)
+        self.assertEqual(renderer.kw['cstruct'], cstruct)
+        self.assertEqual(renderer.kw['options'],
+                         '{"delay": 400, "max": 10, "autoFill": false, "minChars": 1, "mustMatch": false}')
+        self.assertEqual(renderer.kw['values'],
+                         json.dumps(url))
+
+    def test_serialize_iterable(self):
+        try:
+            import json
+        except ImportError: #PRAGMA: nocover
+            import simplejson as json
+        widget = self._makeOne()
+        vals = [1,2,3,4]
+        widget.values = vals
+        renderer = DummyRenderer()
+        schema = DummySchema()
+        field = DummyField(schema, renderer=renderer)
+        cstruct = 'abc'
+        widget.serialize(field, cstruct)
+        self.assertEqual(renderer.template, widget.template)
+        self.assertEqual(renderer.kw['field'], field)
+        self.assertEqual(renderer.kw['cstruct'], cstruct)
+        self.assertEqual(renderer.kw['options'],
+                         '{"delay": 10, "max": 10, "autoFill": false, "minChars": 1, "mustMatch": false}')
+        self.assertEqual(renderer.kw['values'],
+                         json.dumps(vals))
+
+    def test_serialize_not_null_readonly(self):
+        widget = self._makeOne()
+        renderer = DummyRenderer()
+        schema = DummySchema()
+        field = DummyField(schema, renderer=renderer)
+        cstruct = 'abc'
+        widget.serialize(field, cstruct, readonly=True)
+        self.assertEqual(renderer.template, widget.readonly_template)
+        self.assertEqual(renderer.kw['field'], field)
+        self.assertEqual(renderer.kw['cstruct'], cstruct)
+
+    def test_deserialize_strip(self):
+        widget = self._makeOne()
+        field = DummyField()
+        pstruct = ' abc '
+        result = widget.deserialize(field, pstruct)
+        self.assertEqual(result, 'abc')
+
+    def test_deserialize_no_strip(self):
+        widget = self._makeOne(strip=False)
+        field = DummyField()
+        pstruct = ' abc '
+        result = widget.deserialize(field, pstruct)
+        self.assertEqual(result, ' abc ')
+
+    def test_deserialize_null(self):
+        from colander import null
+        widget = self._makeOne(strip=False)
+        field = DummyField()
+        result = widget.deserialize(field, null)
+        self.assertEqual(result, null)
+
+    def test_deserialize_emptystring(self):
+        from colander import null
+        widget = self._makeOne()
+        field = DummyField()
+        pstruct = ''
+        result = widget.deserialize(field, pstruct)
+        self.assertEqual(result, null)
+
+
 class TestDateInputWidget(unittest.TestCase):
     def _makeOne(self, **kw):
         from deform.widget import DateInputWidget

File deform/widget.py

 
 from deform.i18n import _
 
+try:
+    import json 
+except ImportError: # PRAGMA: no cover
+    import simplejson as json 
+
+
 class Widget(object):
     """
     A widget is the building block for rendering logic.  The
             return null
         return pstruct
 
+class AutocompleteInputWidget(Widget):
+    """
+    Renders an ``<input type="text"/>`` widget which provides
+    autocompletion via a list of values.
+
+    When this option is used, the :term:`jquery.autocomplete`
+    library must be loaded into the page serving the form for
+    autocompletion to have any effect.  See :ref:`autocomplete_input`.
+    A version of :term:`jquery.autocomplete` is included in the deform
+    static directory. The default styles for the autocomplete are also
+    available in the deform static/css directory.
+
+    **Attributes/Arguments**
+
+    size
+        The size, in columns, of the text input field.  Defaults to
+        ``None``, meaning that the ``size`` is not included in the
+        widget output (uses browser default size).
+
+    template
+        The template name used to render the widget.  Default:
+        ``autocomplete_textinput``.
+
+    readonly_template
+        The template name used to render the widget in read-only mode.
+        Default: ``readonly/autocomplete_textinput``.
+
+    strip
+        If true, during deserialization, strip the value of leading
+        and trailing whitespace (default ``True``).
+
+    values
+        ``values`` from which :term:`jquery.autocomplete` provides
+        autocompletion. It MUST be an iterable that can be converted
+        to a json array by [simple]json.dumps. It is also possible
+        to pass a [base]string representing a remote URL.
+
+        If ``values`` is a string it will be treated as a
+        URL. If values is an iterable which can be serialized to a
+        :term:`json` array, it will be treated as local data.
+
+        If a string is provided to a URL, an :term:`xhr` request will
+        be sent to the URL. The response should be a list of values
+        one per line. i.e.::
+          foo
+          bar
+          baz
+
+        Defaults to ``None``.
+
+    autofill
+        ``autofill`` is an optional argument to
+        :term:`jquery.autocomplete`. It fills the text input while
+        still typing. The value will be replaced if more is typed or
+        a different selection from the selection area is selected.
+        Defaults to ``False``
+
+    minchars
+        ``minchars``  is an optional argument to
+        :term:`jquery.autocomplete`. The number of characters to wait
+        for before activating the autocomplete call.
+        Defaults to ``1``.
+
+    must_match
+        ``must_match`` is an optional argument to
+        :term:`jquery.autocomplete`. If ``True`` only values in the
+        results will be allowed in the input. Non existant values will
+        result in an empty input. Defaults to ``False``.
+
+    max
+        ``max`` is an optional argument to
+        :term:`jquery.autocomplete`. It sets the maximum number of
+        results to show in the selection area. It also adds the value
+        as a ``limit`` parameter in a remote request.
+        Defaults to ``10``.
+
+    delay
+        ``delay`` is an optional argument to
+        :term:`jquery.autocomplete`. It sets the time to wait after a
+        keypress to activate the autocomplete call.
+        Defaults to ``10`` ms or ``400`` ms if a url is passed.
+    """
+    autofill = False
+    delay = None
+    max = 10
+    minchars = 1
+    must_match = False
+    readonly_template = 'readonly/autocomplete_input'
+    size = None
+    strip = True
+    template = 'autocomplete_input'
+    values = None
+
+    def serialize(self, field, cstruct, readonly=False):
+        if cstruct in (null, None):
+            cstruct = ''
+        options = {}
+        if not self.delay:
+            # set default delay if None
+            options['delay'] = (isinstance(self.values,
+                                          basestring) and 400) or 10
+        options['autoFill'] = self.autofill
+        options['minChars'] = self.minchars
+        options['mustMatch'] = self.must_match
+        options['max'] = self.max
+        options = json.dumps(options)
+        values = json.dumps(self.values)
+        template = readonly and self.readonly_template or self.template
+        return field.renderer(template,
+                              cstruct=cstruct,
+                              field=field,
+                              options=options,
+                              values=values)
+
+    def deserialize(self, field, pstruct):
+        if pstruct is null:
+            return null
+        if self.strip:
+            pstruct = pstruct.strip()
+        if not pstruct:
+            return null
+        return pstruct
+
+
 class DateInputWidget(Widget):
     """
     Renders an ``<input type="date"/>`` date picker widget (uses JQuery Tools

File deformdemo/app.py

         form['text'].widget = deform.widget.TextInputWidget(size=60)
         return self.render_form(form)
 
+    @bfg_view(renderer='templates/form.pt', name='autocomplete_input')
+    @demonstrate('Autocomplete Input Widget')
+    def autocomplete_input(self):
+        class Schema(colander.Schema):
+            text = colander.SchemaNode(
+                colander.String(),
+                validator=colander.Length(max=100),
+                description='Enter some text (Hint: try "b" or "t")')
+        schema = Schema()
+        choices = ['bar', 'baz', 'two', 'three']
+        form = deform.Form(schema, buttons=('submit',))
+        form['text'].widget = deform.widget.AutocompleteInputWidget(
+            size=60,
+            values = choices)
+        return self.render_form(form)
+
+    @bfg_view(renderer='templates/form.pt', name='autocomplete_remote_input')
+    @demonstrate('Autocomplete Input Widget with Remote Data Source')
+    def autocomplete_remote_input(self):
+        class Schema(colander.Schema):
+            text = colander.SchemaNode(
+                colander.String(),
+                validator=colander.Length(max=100),
+                description='Enter some text (Hint: try "b" or "t")')
+        schema = Schema()
+        url = '/autocomplete_input_values'
+        form = deform.Form(schema, buttons=('submit',))
+        form['text'].widget = deform.widget.AutocompleteInputWidget(
+            size=60,
+            values = url)
+        return self.render_form(form)
+
+    @bfg_view(renderer='string', name='autocomplete_input_values')
+    def autocomplete_input_values(self):
+        text = self.request.params.get('q', '')
+        choices = "\n".join([x for x in ['bar', 'baz', 'two', 'three'] 
+                             if x.startswith(text)])
+        return choices
+
     @bfg_view(renderer='templates/form.pt', name='textarea')
     @demonstrate('Text Area Widget')
     def textarea(self):

File deformdemo/templates/main.pt

     <link rel="stylesheet" href="${static}/css/form.css" type="text/css" />
     <link rel="stylesheet" href="${static}/css/theme.css" type="text/css" />
     <link rel="stylesheet" href="${static}/css/dateinput.css" type="text/css" />
+    <link rel="stylesheet" href="${static}/css/jquery.autocomplete.css" type="text/css" />
     <link rel="stylesheet" href="${app_url}/pygments.css" type="text/css" />
     <!-- JavaScript -->
     <script type="text/javascript"
             src="${static}/scripts/jquery.tools.min.js"></script> 
     <script type="text/javascript"
             src="${static}/tinymce/jscripts/tiny_mce/tiny_mce.js"></script> 
+    <script type="text/javascript"
+            src="${static}/scripts/jquery.autocomplete.min.js"></script> 
+
   </head>
   
   <body id="public">

File deformdemo/tests/test_demo.py

         captured = browser.get_text('css=#captured')
         self.assertEqual(captured, "{'text': u'hello'}")
 
+class AutocompleteInputWidgetTests(unittest.TestCase):
+    url = "/autocomplete_input/"
+    def test_render_default(self):
+        browser.open(self.url)
+        browser.wait_for_page_to_load("30000")
+        self.failUnless(browser.is_text_present("Autocomplete Input Widget"))
+        self.assertEqual(browser.get_attribute("deformField1@name"), 'text')
+        self.assertEqual(browser.get_attribute("deformField1@type"), 'text')
+        self.assertEqual(browser.get_value("deformField1"), '')
+        self.assertEqual(browser.get_text('css=.req'), '*')
+        self.assertEqual(browser.get_text('css=#captured'), 'None')
+
+    def test_submit_empty(self):
+        browser.open(self.url)
+        browser.wait_for_page_to_load("30000")
+        browser.click('submit')
+        browser.wait_for_page_to_load("30000")
+        self.failUnless(browser.is_element_present('css=.errorMsgLbl'))
+        self.assertEqual(browser.get_text('css=#error-deformField1'),
+                         'Required')
+        captured = browser.get_text('css=#captured')
+        self.assertEqual(captured, 'None')
+
+    def test_submit_filled(self):
+        browser.open(self.url)
+        browser.wait_for_page_to_load("30000")
+        browser.type_keys('deformField1', 'b')
+        import time
+        time.sleep(.2)
+        self.failUnless(browser.is_text_present('bar'))
+        self.failUnless(browser.is_text_present('baz'))
+        browser.click("//body[@id='public']/div[2]/ul/li[2]")
+        browser.click('submit')
+        browser.wait_for_page_to_load("30000")
+        self.failIf(browser.is_element_present('css=.errorMsgLbl'))
+        self.assertEqual(browser.get_value('deformField1'), u'bar')
+        captured = browser.get_text('css=#captured')
+        self.assertEqual(captured, "{'text': u'bar'}")
+
+class AutocompleteRemoteInputWidgetTests(unittest.TestCase):
+    url = "/autocomplete_remote_input/"
+    def test_render_default(self):
+        browser.open(self.url)
+        browser.wait_for_page_to_load("30000")
+        self.failUnless(browser.is_text_present(
+                "Autocomplete Input Widget with Remote Data Source"))
+        self.assertEqual(browser.get_attribute("deformField1@name"), 'text')
+        self.assertEqual(browser.get_attribute("deformField1@type"), 'text')
+        self.assertEqual(browser.get_value("deformField1"), '')
+        self.assertEqual(browser.get_text('css=.req'), '*')
+        self.assertEqual(browser.get_text('css=#captured'), 'None')
+
+    def test_submit_empty(self):
+        browser.open(self.url)
+        browser.wait_for_page_to_load("30000")
+        browser.click('submit')
+        browser.wait_for_page_to_load("30000")
+        self.failUnless(browser.is_element_present('css=.errorMsgLbl'))
+        self.assertEqual(browser.get_text('css=#error-deformField1'),
+                         'Required')
+        captured = browser.get_text('css=#captured')
+        self.assertEqual(captured, 'None')
+
+    def test_submit_filled(self):
+        browser.open(self.url)
+        browser.wait_for_page_to_load("30000")
+        browser.type_keys('deformField1', 't')
+        import time
+        time.sleep(1)
+        self.failUnless(browser.is_text_present('two'))
+        self.failUnless(browser.is_text_present('three'))
+        browser.click("//body[@id='public']/div[2]/ul/li[1]")
+        browser.click('submit')
+        browser.wait_for_page_to_load("30000")
+        self.failIf(browser.is_element_present('css=.errorMsgLbl'))
+        self.assertEqual(browser.get_value('deformField1'), u'two')
+        captured = browser.get_text('css=#captured')
+        self.assertEqual(captured, "{'text': u'two'}")
+
+
 class TextAreaWidgetTests(unittest.TestCase):
     url = "/textarea/"
     def test_render_default(self):

File docs/api.rst

 .. autoclass:: TextInputWidget
    :members:
 
+.. autoclass:: AutocompleteInputWidget
+   :members:
+
 .. autoclass:: HiddenWidget
    :members:
 

File docs/basics.rst

 must be checked at input time a separate :term:`validator` should be
 attached to the related schema node.
 
+
+.. _autocomplete_input:
+
+Using :class:`deform.widget.AutocompleteInputWidget`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :class:`deform.widget.AutocompleteInputWidget` widget allows for
+client side autocompletion from provided choices in a text input
+field. To use this you **MUST** ensure that :term:`jQuery` and the
+:term:`jquery.autocomplete` plugin are available to the page where the
+:class:`deform.widget.AutocompleteInputWidget` widget is rendered.
+
+For confenience a version of the :term:`jquery.autocomplete` is
+included in the :mod:`deform` static directory. Additionally, the
+:term:`jquery.autocomplete` styles for the selection box are also
+included in :mod:`deform` static. See
+:ref:`_using_deform_static_library` for more information about using
+included libraries from your application.
+
+A very simple example of using
+:class:`deform.widget.AutocompleteInputWidget` follows:
+
+.. code-block:: python
+
+   form['frozznobs'].widget = AutocompleteInputWidget(
+                                values=['spam', 'eggs', 'bar', 'baz'])
+
+Instead of a list of values a URL can be provided to values:
+
+.. code-block:: python
+
+   form['frobsnozz'].widget = AutocompleteInputWidget(
+                                values='http://example.com/someapi')
+
+In the above case a call to the url should provide results one item
+per line in the response. Something like::
+
+    item-one
+    item-two
+    item-three
+
+
+Some options for the :term:`jquery.autocomplete` plugin are mapped and
+can be passed to the widget. See
+:class:`deform.widget.AutocompleteInputWidget` for details regarding the
+available options. Passing options looks like:
+
+.. code-block:: python
+
+   form['nobsfrozz'].widget = AutocompleteInputWidget(
+				values=['spam, 'eggs', 'bar', 'baz'],
+                                options={'autofill' : True})
+
+When :class:`deform.widget.AutocompleteInputWidget` is used, the
+:term:`jquery.autocomplete` library must be loaded into the page
+serving the form for the mask argument to have any effect.  A copy
+ of this library is available in the ``static/scripts`` directory of
+the :mod:`deform` package itself.
+
+See `http://localhost:8521/autocomplete_input/
+<http://localhost:8521/autocomplete_input/>`_ and
+`http://localhost:8521/autocomplete_remote_input/
+<http://localhost:8521/autocomplete_remote_input/>`_
+for working examples. A working example of a remote URL providing
+completion data can br found at
+`http://localhost:8521/autocomplete_input_values
+<http://localhost:8521/autocomplete_input_values>`_.	
+
+Use of :class:`deform.widget.AutocompleteInputWidget` is not a
+replacement for server-side validation of the field; it is purely a UI
+affordance.  If the data must be checked at input time a separate
+:term:`validator` should be attached to the related schema node.
+
+
+
+
 Creating a New Schema Type
 --------------------------
 

File docs/conf.py

 # other places throughout the built documents.
 #
 # The short X.Y version.
-version = '0.3'
+version = '0.4'
 # The full version, including alpha/beta/rc tags.
 release = version
 

File docs/glossary.rst

      has the ability to convert HTML TEXTAREA fields or other HTML
      elements to editor instances. TinyMCE is very easy to integrate
      into other Content Management Systems.
+
+   jquery.autocomplete
+     A  :term:`jQuery` plugin library that allows for autocompleting a
+     value in a text input, making it easier to find and select a
+     value from a possibly large list. The data may be local or
+     remote. See also `http://docs.jquery.com/Plugins/Autocomplete
+     <http://docs.jquery.com/Plugins/Autocomplete>`_ for more details.
+
+   xhr
+     ``xhr`` an XMLHTTPRequest. See also
+     `http://www.w3.org/TR/XMLHttpRequest/ 
+     <http://www.w3.org/TR/XMLHttpRequest/>`_.
+
+   JSON
+     ``JSON`` (JavaScript Object Notation) is a lightweight
+     data-interchange format. It is easy for humans to read and write.
+     See also `http://www.json.org/ <http://www.json.org/>`_.
+
+   jQuery
+     `jQuery <http://jquery.com/>`_ is a JavaScript library for making
+     client side changes to HTML.
 ##############################################################################
 
 import os
+import sys
 
 from setuptools import setup
 from setuptools import find_packages
     'peppercorn',
     ]
 
+if sys.version_info <(2,6,0):
+    requires.append('simplejson')
+
 setupkw = dict(
     name='deform',
     version='0.3',