muck-comments 0.1.17 → 0.1.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/README.rdoc +2 -1
  2. data/VERSION +1 -1
  3. data/app/views/comments/_tiny_mce_form.html.erb +9 -9
  4. data/muck-comments.gemspec +88 -4
  5. data/test/rails_root/config/environment.rb +2 -0
  6. data/test/rails_root/config/initializers/geokit_config.rb +63 -0
  7. data/test/rails_root/db/migrate/20091124203137_add_location_to_profiles.rb +15 -0
  8. data/test/rails_root/db/migrate/20091124205819_add_fields_to_profiles.rb +21 -0
  9. data/test/rails_root/public/images/fancybox/fancy_shadow_e.png +0 -0
  10. data/test/rails_root/public/images/fancybox/fancy_shadow_n.png +0 -0
  11. data/test/rails_root/public/images/fancybox/fancy_shadow_ne.png +0 -0
  12. data/test/rails_root/public/images/fancybox/fancy_shadow_nw.png +0 -0
  13. data/test/rails_root/public/images/fancybox/fancy_shadow_s.png +0 -0
  14. data/test/rails_root/public/images/fancybox/fancy_shadow_se.png +0 -0
  15. data/test/rails_root/public/images/fancybox/fancy_shadow_sw.png +0 -0
  16. data/test/rails_root/public/images/fancybox/fancy_shadow_w.png +0 -0
  17. data/test/rails_root/public/javascripts/jquery/colorpicker.js +450 -0
  18. data/test/rails_root/public/javascripts/jquery/jquery.autocomplete.js.readme +6 -0
  19. data/test/rails_root/public/javascripts/jquery/jquery.autocomplete.min.js +15 -0
  20. data/test/rails_root/public/javascripts/jquery/jquery.autocomplete.pack.js +767 -0
  21. data/test/rails_root/public/javascripts/jquery/jquery.fancybox.js +13 -6
  22. data/test/rails_root/public/javascripts/jquery/jquery.metadata.min.js +13 -0
  23. data/test/rails_root/public/javascripts/jquery/jquery.queryString.js +33 -0
  24. data/test/rails_root/public/javascripts/jquery/jquery.swapimage.js +66 -0
  25. data/test/rails_root/public/javascripts/jquery/jquery.swapimage.min.js +11 -0
  26. data/test/rails_root/public/javascripts/jquery/jquery.tips.js +7 -6
  27. data/test/rails_root/public/javascripts/muck.js +48 -2
  28. data/test/rails_root/public/javascripts/tree.js +107 -0
  29. data/test/rails_root/public/stylesheets/jquery/jquery.autocomplete.css +15 -0
  30. data/test/rails_root/public/stylesheets/jquery/jquery.fancybox.css +19 -25
  31. data/test/rails_root/public/stylesheets/muck-activities.css +1 -1
  32. data/test/rails_root/test/unit/comment_test.rb +3 -3
  33. data/test/rails_root/vendor/plugins/geokit-rails/CHANGELOG.rdoc +46 -0
  34. data/test/rails_root/vendor/plugins/geokit-rails/MIT-LICENSE +20 -0
  35. data/test/rails_root/vendor/plugins/geokit-rails/README.markdown +561 -0
  36. data/test/rails_root/vendor/plugins/geokit-rails/Rakefile +18 -0
  37. data/test/rails_root/vendor/plugins/geokit-rails/about.yml +9 -0
  38. data/test/rails_root/vendor/plugins/geokit-rails/assets/api_keys_template +61 -0
  39. data/test/rails_root/vendor/plugins/geokit-rails/init.rb +2 -0
  40. data/test/rails_root/vendor/plugins/geokit-rails/install.rb +14 -0
  41. data/test/rails_root/vendor/plugins/geokit-rails/lib/geokit-rails.rb +26 -0
  42. data/test/rails_root/vendor/plugins/geokit-rails/lib/geokit-rails/acts_as_mappable.rb +456 -0
  43. data/test/rails_root/vendor/plugins/geokit-rails/lib/geokit-rails/adapters/abstract.rb +31 -0
  44. data/test/rails_root/vendor/plugins/geokit-rails/lib/geokit-rails/adapters/mysql.rb +22 -0
  45. data/test/rails_root/vendor/plugins/geokit-rails/lib/geokit-rails/adapters/postgresql.rb +22 -0
  46. data/test/rails_root/vendor/plugins/geokit-rails/lib/geokit-rails/adapters/sqlserver.rb +43 -0
  47. data/test/rails_root/vendor/plugins/geokit-rails/lib/geokit-rails/defaults.rb +22 -0
  48. data/test/rails_root/vendor/plugins/geokit-rails/lib/geokit-rails/geocoder_control.rb +16 -0
  49. data/test/rails_root/vendor/plugins/geokit-rails/lib/geokit-rails/ip_geocode_lookup.rb +46 -0
  50. data/test/rails_root/vendor/plugins/geokit-rails/test/acts_as_mappable_test.rb +474 -0
  51. data/test/rails_root/vendor/plugins/geokit-rails/test/boot.rb +25 -0
  52. data/test/rails_root/vendor/plugins/geokit-rails/test/database.yml +20 -0
  53. data/test/rails_root/vendor/plugins/geokit-rails/test/fixtures/companies.yml +7 -0
  54. data/test/rails_root/vendor/plugins/geokit-rails/test/fixtures/custom_locations.yml +54 -0
  55. data/test/rails_root/vendor/plugins/geokit-rails/test/fixtures/locations.yml +54 -0
  56. data/test/rails_root/vendor/plugins/geokit-rails/test/fixtures/mock_addresses.yml +17 -0
  57. data/test/rails_root/vendor/plugins/geokit-rails/test/fixtures/mock_families.yml +2 -0
  58. data/test/rails_root/vendor/plugins/geokit-rails/test/fixtures/mock_houses.yml +9 -0
  59. data/test/rails_root/vendor/plugins/geokit-rails/test/fixtures/mock_organizations.yml +5 -0
  60. data/test/rails_root/vendor/plugins/geokit-rails/test/fixtures/mock_people.yml +5 -0
  61. data/test/rails_root/vendor/plugins/geokit-rails/test/fixtures/stores.yml +0 -0
  62. data/test/rails_root/vendor/plugins/geokit-rails/test/ip_geocode_lookup_test.rb +77 -0
  63. data/test/rails_root/vendor/plugins/geokit-rails/test/models/company.rb +3 -0
  64. data/test/rails_root/vendor/plugins/geokit-rails/test/models/custom_location.rb +12 -0
  65. data/test/rails_root/vendor/plugins/geokit-rails/test/models/location.rb +4 -0
  66. data/test/rails_root/vendor/plugins/geokit-rails/test/models/mock_address.rb +4 -0
  67. data/test/rails_root/vendor/plugins/geokit-rails/test/models/mock_family.rb +3 -0
  68. data/test/rails_root/vendor/plugins/geokit-rails/test/models/mock_house.rb +3 -0
  69. data/test/rails_root/vendor/plugins/geokit-rails/test/models/mock_organization.rb +4 -0
  70. data/test/rails_root/vendor/plugins/geokit-rails/test/models/mock_person.rb +4 -0
  71. data/test/rails_root/vendor/plugins/geokit-rails/test/models/store.rb +3 -0
  72. data/test/rails_root/vendor/plugins/geokit-rails/test/schema.rb +60 -0
  73. data/test/rails_root/vendor/plugins/geokit-rails/test/tasks.rake +31 -0
  74. data/test/rails_root/vendor/plugins/geokit-rails/test/test_helper.rb +23 -0
  75. metadata +85 -2
@@ -0,0 +1,6 @@
1
+ NOTE: the jquery.autocomplete.pack.js file is modified from the original to work with luvfoo. Specifically line
2
+ 314 was changed from
3
+ if ( data && data.length && hasFocus ) {
4
+ to
5
+ if ( data && data.length ) {
6
+ For some reason the hasFocus value was always 0 which prevented the autocomplete from ever showing up.
@@ -0,0 +1,15 @@
1
+ /*
2
+ * Autocomplete - jQuery plugin 1.0.2
3
+ *
4
+ * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
5
+ *
6
+ * Dual licensed under the MIT and GPL licenses:
7
+ * http://www.opensource.org/licenses/mit-license.php
8
+ * http://www.gnu.org/licenses/gpl.html
9
+ *
10
+ * Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $
11
+ *
12
+ */;(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){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){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];}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));$.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){$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
13
+ $input.val("");}});}if(wasVisible)$.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);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(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
14
+ if(data[q]){return data[q];}else
15
+ 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();}};};$.Autocompleter.Selection=function(field,start,end){if(field.createTextRange){var selRange=field.createTextRange();selRange.collapse(true);selRange.moveStart("character",start);selRange.moveEnd("character",end);selRange.select();}else if(field.setSelectionRange){field.setSelectionRange(start,end);}else{if(field.selectionStart){field.selectionStart=start;field.selectionEnd=end;}}field.focus();};})(jQuery);
@@ -0,0 +1,767 @@
1
+ /**********************************************************************************************
2
+ * NOTE: this version is modified from the original to work with luvfoo. Specifically line
3
+ * 314 was changed from
4
+ * if ( data && data.length && hasFocus ) {
5
+ * to
6
+ * if ( data && data.length ) {
7
+ * For some reason the hasFocus value was always 0 which prevented the autocomplete from ever showing up.
8
+ **********************************************************************************************
9
+ *
10
+ * Autocomplete - jQuery plugin 1.0.2
11
+ *
12
+ * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
13
+ *
14
+ * Dual licensed under the MIT and GPL licenses:
15
+ * http://www.opensource.org/licenses/mit-license.php
16
+ * http://www.gnu.org/licenses/gpl.html
17
+ *
18
+ * Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $
19
+ *
20
+ */
21
+
22
+ ;(function($) {
23
+
24
+ $.fn.extend({
25
+ autocomplete: function(urlOrData, options) {
26
+ var isUrl = typeof urlOrData == "string";
27
+ options = $.extend({}, $.Autocompleter.defaults, {
28
+ url: isUrl ? urlOrData : null,
29
+ data: isUrl ? null : urlOrData,
30
+ delay: isUrl ? $.Autocompleter.defaults.delay : 10,
31
+ max: options && !options.scroll ? 10 : 150
32
+ }, options);
33
+
34
+ // if highlight is set to false, replace it with a do-nothing function
35
+ options.highlight = options.highlight || function(value) { return value; };
36
+
37
+ // if the formatMatch option is not specified, then use formatItem for backwards compatibility
38
+ options.formatMatch = options.formatMatch || options.formatItem;
39
+
40
+ return this.each(function() {
41
+ new $.Autocompleter(this, options);
42
+ });
43
+ },
44
+ result: function(handler) {
45
+ return this.bind("result", handler);
46
+ },
47
+ search: function(handler) {
48
+ return this.trigger("search", [handler]);
49
+ },
50
+ flushCache: function() {
51
+ return this.trigger("flushCache");
52
+ },
53
+ setOptions: function(options){
54
+ return this.trigger("setOptions", [options]);
55
+ },
56
+ unautocomplete: function() {
57
+ return this.trigger("unautocomplete");
58
+ }
59
+ });
60
+
61
+ $.Autocompleter = function(input, options) {
62
+
63
+ var KEY = {
64
+ UP: 38,
65
+ DOWN: 40,
66
+ DEL: 46,
67
+ TAB: 9,
68
+ RETURN: 13,
69
+ ESC: 27,
70
+ COMMA: 188,
71
+ PAGEUP: 33,
72
+ PAGEDOWN: 34,
73
+ BACKSPACE: 8
74
+ };
75
+
76
+ // Create $ object for input element
77
+ var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
78
+
79
+ var timeout;
80
+ var previousValue = "";
81
+ var cache = $.Autocompleter.Cache(options);
82
+ var hasFocus = 0;
83
+ var lastKeyPressCode;
84
+ var config = {
85
+ mouseDownOnSelect: false
86
+ };
87
+ var select = $.Autocompleter.Select(options, input, selectCurrent, config);
88
+
89
+ var blockSubmit;
90
+
91
+ // prevent form submit in opera when selecting with return key
92
+ $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
93
+ if (blockSubmit) {
94
+ blockSubmit = false;
95
+ return false;
96
+ }
97
+ });
98
+
99
+ // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
100
+ $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
101
+ // track last key pressed
102
+ lastKeyPressCode = event.keyCode;
103
+ switch(event.keyCode) {
104
+
105
+ case KEY.UP:
106
+ event.preventDefault();
107
+ if ( select.visible() ) {
108
+ select.prev();
109
+ } else {
110
+ onChange(0, true);
111
+ }
112
+ break;
113
+
114
+ case KEY.DOWN:
115
+ event.preventDefault();
116
+ if ( select.visible() ) {
117
+ select.next();
118
+ } else {
119
+ onChange(0, true);
120
+ }
121
+ break;
122
+
123
+ case KEY.PAGEUP:
124
+ event.preventDefault();
125
+ if ( select.visible() ) {
126
+ select.pageUp();
127
+ } else {
128
+ onChange(0, true);
129
+ }
130
+ break;
131
+
132
+ case KEY.PAGEDOWN:
133
+ event.preventDefault();
134
+ if ( select.visible() ) {
135
+ select.pageDown();
136
+ } else {
137
+ onChange(0, true);
138
+ }
139
+ break;
140
+
141
+ // matches also semicolon
142
+ case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
143
+ case KEY.TAB:
144
+ case KEY.RETURN:
145
+ if( selectCurrent() ) {
146
+ // stop default to prevent a form submit, Opera needs special handling
147
+ event.preventDefault();
148
+ blockSubmit = true;
149
+ return false;
150
+ }
151
+ break;
152
+
153
+ case KEY.ESC:
154
+ select.hide();
155
+ break;
156
+
157
+ default:
158
+ clearTimeout(timeout);
159
+ timeout = setTimeout(onChange, options.delay);
160
+ break;
161
+ }
162
+ }).focus(function(){
163
+ // track whether the field has focus, we shouldn't process any
164
+ // results if the field no longer has focus
165
+ hasFocus++;
166
+ }).blur(function() {
167
+ hasFocus = 0;
168
+ if (!config.mouseDownOnSelect) {
169
+ hideResults();
170
+ }
171
+ }).click(function() {
172
+ // show select when clicking in a focused field
173
+ if ( hasFocus++ > 1 && !select.visible() ) {
174
+ onChange(0, true);
175
+ }
176
+ }).bind("search", function() {
177
+ // TODO why not just specifying both arguments?
178
+ var fn = (arguments.length > 1) ? arguments[1] : null;
179
+ function findValueCallback(q, data) {
180
+ var result;
181
+ if( data && data.length ) {
182
+ for (var i=0; i < data.length; i++) {
183
+ if( data[i].result.toLowerCase() == q.toLowerCase() ) {
184
+ result = data[i];
185
+ break;
186
+ }
187
+ }
188
+ }
189
+ if( typeof fn == "function" ) fn(result);
190
+ else $input.trigger("result", result && [result.data, result.value]);
191
+ }
192
+ $.each(trimWords($input.val()), function(i, value) {
193
+ request(value, findValueCallback, findValueCallback);
194
+ });
195
+ }).bind("flushCache", function() {
196
+ cache.flush();
197
+ }).bind("setOptions", function() {
198
+ $.extend(options, arguments[1]);
199
+ // if we've updated the data, repopulate
200
+ if ( "data" in arguments[1] )
201
+ cache.populate();
202
+ }).bind("unautocomplete", function() {
203
+ select.unbind();
204
+ $input.unbind();
205
+ $(input.form).unbind(".autocomplete");
206
+ });
207
+
208
+
209
+ function selectCurrent() {
210
+ var selected = select.selected();
211
+ if( !selected )
212
+ return false;
213
+
214
+ var v = selected.result;
215
+ previousValue = v;
216
+
217
+ if ( options.multiple ) {
218
+ var words = trimWords($input.val());
219
+ if ( words.length > 1 ) {
220
+ v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
221
+ }
222
+ v += options.multipleSeparator;
223
+ }
224
+
225
+ $input.val(v);
226
+ hideResultsNow();
227
+ $input.trigger("result", [selected.data, selected.value]);
228
+ return true;
229
+ }
230
+
231
+ function onChange(crap, skipPrevCheck) {
232
+ if( lastKeyPressCode == KEY.DEL ) {
233
+ select.hide();
234
+ return;
235
+ }
236
+
237
+ var currentValue = $input.val();
238
+
239
+ if ( !skipPrevCheck && currentValue == previousValue )
240
+ return;
241
+
242
+ previousValue = currentValue;
243
+
244
+ currentValue = lastWord(currentValue);
245
+ if ( currentValue.length >= options.minChars) {
246
+ $input.addClass(options.loadingClass);
247
+ if (!options.matchCase)
248
+ currentValue = currentValue.toLowerCase();
249
+ request(currentValue, receiveData, hideResultsNow);
250
+ } else {
251
+ stopLoading();
252
+ select.hide();
253
+ }
254
+ };
255
+
256
+ function trimWords(value) {
257
+ if ( !value ) {
258
+ return [""];
259
+ }
260
+ var words = value.split( options.multipleSeparator );
261
+ var result = [];
262
+ $.each(words, function(i, value) {
263
+ if ( $.trim(value) )
264
+ result[i] = $.trim(value);
265
+ });
266
+ return result;
267
+ }
268
+
269
+ function lastWord(value) {
270
+ if ( !options.multiple )
271
+ return value;
272
+ var words = trimWords(value);
273
+ return words[words.length - 1];
274
+ }
275
+
276
+ // fills in the input box w/the first match (assumed to be the best match)
277
+ // q: the term entered
278
+ // sValue: the first matching result
279
+ function autoFill(q, sValue){
280
+ // autofill in the complete box w/the first match as long as the user hasn't entered in more data
281
+ // if the last user key pressed was backspace, don't autofill
282
+ if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
283
+ // fill in the value (keep the case the user has typed)
284
+ $input.val($input.val() + sValue.substring(lastWord(previousValue).length));
285
+ // select the portion of the value not typed by the user (so the next character will erase)
286
+ $.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
287
+ }
288
+ };
289
+
290
+ function hideResults() {
291
+ clearTimeout(timeout);
292
+ timeout = setTimeout(hideResultsNow, 200);
293
+ };
294
+
295
+ function hideResultsNow() {
296
+ var wasVisible = select.visible();
297
+ select.hide();
298
+ clearTimeout(timeout);
299
+ stopLoading();
300
+ if (options.mustMatch) {
301
+ // call search and run callback
302
+ $input.search(
303
+ function (result){
304
+ // if no value found, clear the input box
305
+ if( !result ) {
306
+ if (options.multiple) {
307
+ var words = trimWords($input.val()).slice(0, -1);
308
+ $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
309
+ }
310
+ else
311
+ $input.val( "" );
312
+ }
313
+ }
314
+ );
315
+ }
316
+ if (wasVisible)
317
+ // position cursor at end of input field
318
+ $.Autocompleter.Selection(input, input.value.length, input.value.length);
319
+ };
320
+
321
+ function receiveData(q, data) {
322
+ if ( data && data.length ) {
323
+ stopLoading();
324
+ select.display(data, q);
325
+ autoFill(q, data[0].value);
326
+ select.show();
327
+ } else {
328
+ hideResultsNow();
329
+ }
330
+ };
331
+
332
+ function request(term, success, failure) {
333
+ if (!options.matchCase)
334
+ term = term.toLowerCase();
335
+ var data = cache.load(term);
336
+ // recieve the cached data
337
+ if (data && data.length) {
338
+ success(term, data);
339
+ // if an AJAX url has been supplied, try loading the data now
340
+ } else if( (typeof options.url == "string") && (options.url.length > 0) ){
341
+
342
+ var extraParams = {
343
+ timestamp: +new Date()
344
+ };
345
+ $.each(options.extraParams, function(key, param) {
346
+ extraParams[key] = typeof param == "function" ? param() : param;
347
+ });
348
+
349
+ $.ajax({
350
+ // try to leverage ajaxQueue plugin to abort previous requests
351
+ mode: "abort",
352
+ // limit abortion to this input
353
+ port: "autocomplete" + input.name,
354
+ dataType: options.dataType,
355
+ url: options.url,
356
+ data: $.extend({
357
+ q: lastWord(term),
358
+ limit: options.max
359
+ }, extraParams),
360
+ success: function(data) {
361
+ var parsed = options.parse && options.parse(data) || parse(data);
362
+ cache.add(term, parsed);
363
+ success(term, parsed);
364
+ }
365
+ });
366
+ } else {
367
+ // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
368
+ select.emptyList();
369
+ failure(term);
370
+ }
371
+ };
372
+
373
+ function parse(data) {
374
+ var parsed = [];
375
+ var rows = data.split("\n");
376
+ for (var i=0; i < rows.length; i++) {
377
+ var row = $.trim(rows[i]);
378
+ if (row) {
379
+ row = row.split("|");
380
+ parsed[parsed.length] = {
381
+ data: row,
382
+ value: row[0],
383
+ result: options.formatResult && options.formatResult(row, row[0]) || row[0]
384
+ };
385
+ }
386
+ }
387
+ return parsed;
388
+ };
389
+
390
+ function stopLoading() {
391
+ $input.removeClass(options.loadingClass);
392
+ };
393
+
394
+ };
395
+
396
+ $.Autocompleter.defaults = {
397
+ inputClass: "ac_input",
398
+ resultsClass: "ac_results",
399
+ loadingClass: "ac_loading",
400
+ minChars: 1,
401
+ delay: 400,
402
+ matchCase: false,
403
+ matchSubset: true,
404
+ matchContains: false,
405
+ cacheLength: 10,
406
+ max: 100,
407
+ mustMatch: false,
408
+ extraParams: {},
409
+ selectFirst: true,
410
+ formatItem: function(row) { return row[0]; },
411
+ formatMatch: null,
412
+ autoFill: false,
413
+ width: 0,
414
+ multiple: false,
415
+ multipleSeparator: ", ",
416
+ highlight: function(value, term) {
417
+ return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
418
+ },
419
+ scroll: true,
420
+ scrollHeight: 180
421
+ };
422
+
423
+ $.Autocompleter.Cache = function(options) {
424
+
425
+ var data = {};
426
+ var length = 0;
427
+
428
+ function matchSubset(s, sub) {
429
+ if (!options.matchCase)
430
+ s = s.toLowerCase();
431
+ var i = s.indexOf(sub);
432
+ if (i == -1) return false;
433
+ return i == 0 || options.matchContains;
434
+ };
435
+
436
+ function add(q, value) {
437
+ if (length > options.cacheLength){
438
+ flush();
439
+ }
440
+ if (!data[q]){
441
+ length++;
442
+ }
443
+ data[q] = value;
444
+ }
445
+
446
+ function populate(){
447
+ if( !options.data ) return false;
448
+ // track the matches
449
+ var stMatchSets = {},
450
+ nullData = 0;
451
+
452
+ // no url was specified, we need to adjust the cache length to make sure it fits the local data store
453
+ if( !options.url ) options.cacheLength = 1;
454
+
455
+ // track all options for minChars = 0
456
+ stMatchSets[""] = [];
457
+
458
+ // loop through the array and create a lookup structure
459
+ for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
460
+ var rawValue = options.data[i];
461
+ // if rawValue is a string, make an array otherwise just reference the array
462
+ rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
463
+
464
+ var value = options.formatMatch(rawValue, i+1, options.data.length);
465
+ if ( value === false )
466
+ continue;
467
+
468
+ var firstChar = value.charAt(0).toLowerCase();
469
+ // if no lookup array for this character exists, look it up now
470
+ if( !stMatchSets[firstChar] )
471
+ stMatchSets[firstChar] = [];
472
+
473
+ // if the match is a string
474
+ var row = {
475
+ value: value,
476
+ data: rawValue,
477
+ result: options.formatResult && options.formatResult(rawValue) || value
478
+ };
479
+
480
+ // push the current match into the set list
481
+ stMatchSets[firstChar].push(row);
482
+
483
+ // keep track of minChars zero items
484
+ if ( nullData++ < options.max ) {
485
+ stMatchSets[""].push(row);
486
+ }
487
+ };
488
+
489
+ // add the data items to the cache
490
+ $.each(stMatchSets, function(i, value) {
491
+ // increase the cache size
492
+ options.cacheLength++;
493
+ // add to the cache
494
+ add(i, value);
495
+ });
496
+ }
497
+
498
+ // populate any existing data
499
+ setTimeout(populate, 25);
500
+
501
+ function flush(){
502
+ data = {};
503
+ length = 0;
504
+ }
505
+
506
+ return {
507
+ flush: flush,
508
+ add: add,
509
+ populate: populate,
510
+ load: function(q) {
511
+ if (!options.cacheLength || !length)
512
+ return null;
513
+ /*
514
+ * if dealing w/local data and matchContains than we must make sure
515
+ * to loop through all the data collections looking for matches
516
+ */
517
+ if( !options.url && options.matchContains ){
518
+ // track all matches
519
+ var csub = [];
520
+ // loop through all the data grids for matches
521
+ for( var k in data ){
522
+ // don't search through the stMatchSets[""] (minChars: 0) cache
523
+ // this prevents duplicates
524
+ if( k.length > 0 ){
525
+ var c = data[k];
526
+ $.each(c, function(i, x) {
527
+ // if we've got a match, add it to the array
528
+ if (matchSubset(x.value, q)) {
529
+ csub.push(x);
530
+ }
531
+ });
532
+ }
533
+ }
534
+ return csub;
535
+ } else
536
+ // if the exact item exists, use it
537
+ if (data[q]){
538
+ return data[q];
539
+ } else
540
+ if (options.matchSubset) {
541
+ for (var i = q.length - 1; i >= options.minChars; i--) {
542
+ var c = data[q.substr(0, i)];
543
+ if (c) {
544
+ var csub = [];
545
+ $.each(c, function(i, x) {
546
+ if (matchSubset(x.value, q)) {
547
+ csub[csub.length] = x;
548
+ }
549
+ });
550
+ return csub;
551
+ }
552
+ }
553
+ }
554
+ return null;
555
+ }
556
+ };
557
+ };
558
+
559
+ $.Autocompleter.Select = function (options, input, select, config) {
560
+ var CLASSES = {
561
+ ACTIVE: "ac_over"
562
+ };
563
+
564
+ var listItems,
565
+ active = -1,
566
+ data,
567
+ term = "",
568
+ needsInit = true,
569
+ element,
570
+ list;
571
+
572
+ // Create results
573
+ function init() {
574
+ if (!needsInit)
575
+ return;
576
+ element = $("<div/>")
577
+ .hide()
578
+ .addClass(options.resultsClass)
579
+ .css("position", "absolute")
580
+ .appendTo(document.body);
581
+
582
+ list = $("<ul/>").appendTo(element).mouseover( function(event) {
583
+ if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
584
+ active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
585
+ $(target(event)).addClass(CLASSES.ACTIVE);
586
+ }
587
+ }).click(function(event) {
588
+ $(target(event)).addClass(CLASSES.ACTIVE);
589
+ select();
590
+ // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
591
+ input.focus();
592
+ return false;
593
+ }).mousedown(function() {
594
+ config.mouseDownOnSelect = true;
595
+ }).mouseup(function() {
596
+ config.mouseDownOnSelect = false;
597
+ });
598
+
599
+ if( options.width > 0 )
600
+ element.css("width", options.width);
601
+
602
+ needsInit = false;
603
+ }
604
+
605
+ function target(event) {
606
+ var element = event.target;
607
+ while(element && element.tagName != "LI")
608
+ element = element.parentNode;
609
+ // more fun with IE, sometimes event.target is empty, just ignore it then
610
+ if(!element)
611
+ return [];
612
+ return element;
613
+ }
614
+
615
+ function moveSelect(step) {
616
+ listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
617
+ movePosition(step);
618
+ var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
619
+ if(options.scroll) {
620
+ var offset = 0;
621
+ listItems.slice(0, active).each(function() {
622
+ offset += this.offsetHeight;
623
+ });
624
+ if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
625
+ list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
626
+ } else if(offset < list.scrollTop()) {
627
+ list.scrollTop(offset);
628
+ }
629
+ }
630
+ };
631
+
632
+ function movePosition(step) {
633
+ active += step;
634
+ if (active < 0) {
635
+ active = listItems.size() - 1;
636
+ } else if (active >= listItems.size()) {
637
+ active = 0;
638
+ }
639
+ }
640
+
641
+ function limitNumberOfItems(available) {
642
+ return options.max && options.max < available
643
+ ? options.max
644
+ : available;
645
+ }
646
+
647
+ function fillList() {
648
+ list.empty();
649
+ var max = limitNumberOfItems(data.length);
650
+ for (var i=0; i < max; i++) {
651
+ if (!data[i])
652
+ continue;
653
+ var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
654
+ if ( formatted === false )
655
+ continue;
656
+ var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
657
+ $.data(li, "ac_data", data[i]);
658
+ }
659
+ listItems = list.find("li");
660
+ if ( options.selectFirst ) {
661
+ listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
662
+ active = 0;
663
+ }
664
+ // apply bgiframe if available
665
+ if ( $.fn.bgiframe )
666
+ list.bgiframe();
667
+ }
668
+
669
+ return {
670
+ display: function(d, q) {
671
+ init();
672
+ data = d;
673
+ term = q;
674
+ fillList();
675
+ },
676
+ next: function() {
677
+ moveSelect(1);
678
+ },
679
+ prev: function() {
680
+ moveSelect(-1);
681
+ },
682
+ pageUp: function() {
683
+ if (active != 0 && active - 8 < 0) {
684
+ moveSelect( -active );
685
+ } else {
686
+ moveSelect(-8);
687
+ }
688
+ },
689
+ pageDown: function() {
690
+ if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
691
+ moveSelect( listItems.size() - 1 - active );
692
+ } else {
693
+ moveSelect(8);
694
+ }
695
+ },
696
+ hide: function() {
697
+ element && element.hide();
698
+ listItems && listItems.removeClass(CLASSES.ACTIVE);
699
+ active = -1;
700
+ },
701
+ visible : function() {
702
+ return element && element.is(":visible");
703
+ },
704
+ current: function() {
705
+ return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
706
+ },
707
+ show: function() {
708
+ var offset = $(input).offset();
709
+ element.css({
710
+ width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
711
+ top: offset.top + input.offsetHeight,
712
+ left: offset.left
713
+ }).show();
714
+ if(options.scroll) {
715
+ list.scrollTop(0);
716
+ list.css({
717
+ maxHeight: options.scrollHeight,
718
+ overflow: 'auto'
719
+ });
720
+
721
+ if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
722
+ var listHeight = 0;
723
+ listItems.each(function() {
724
+ listHeight += this.offsetHeight;
725
+ });
726
+ var scrollbarsVisible = listHeight > options.scrollHeight;
727
+ list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
728
+ if (!scrollbarsVisible) {
729
+ // IE doesn't recalculate width when scrollbar disappears
730
+ listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
731
+ }
732
+ }
733
+
734
+ }
735
+ },
736
+ selected: function() {
737
+ var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
738
+ return selected && selected.length && $.data(selected[0], "ac_data");
739
+ },
740
+ emptyList: function (){
741
+ list && list.empty();
742
+ },
743
+ unbind: function() {
744
+ element && element.remove();
745
+ }
746
+ };
747
+ };
748
+
749
+ $.Autocompleter.Selection = function(field, start, end) {
750
+ if( field.createTextRange ){
751
+ var selRange = field.createTextRange();
752
+ selRange.collapse(true);
753
+ selRange.moveStart("character", start);
754
+ selRange.moveEnd("character", end);
755
+ selRange.select();
756
+ } else if( field.setSelectionRange ){
757
+ field.setSelectionRange(start, end);
758
+ } else {
759
+ if( field.selectionStart ){
760
+ field.selectionStart = start;
761
+ field.selectionEnd = end;
762
+ }
763
+ }
764
+ field.focus();
765
+ };
766
+
767
+ })(jQuery);