Skip to content

Commit

Permalink
added 'Ajax Autocomplete for Prototype' version 1.0.3. this should re…
Browse files Browse the repository at this point in the history
…place everywhere the standard rails autocomplete plugin.
  • Loading branch information
elijh committed May 28, 2009
1 parent 80c6903 commit 25d8667
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 25 deletions.
23 changes: 17 additions & 6 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ Seth Walker
Crabgrass includes many libraries and images created by third parties
and distributed under various licenses.

rails
http://rubyonrails.org
Copyright© 2004 David Heinemeier Hansson
http://opensource.org/licenses/mit-license.php

None of these are AGPL, although all are free licenses as determined by the
Free Software Foundation. You may modify these libaries and files without
redistributing your changes.

== icons and css

tango icons
http://tango.freedesktop.org
Creative Commons Attribution ShareAlike 2.5
Expand All @@ -46,8 +47,18 @@ Whatever:hover
(c) 2005 - Peter Nederlof http://www.xs4all.nl/~peterned
GNU Lesser General Public v2.1 or >

== javascript

Prototype.js
http://www.prototypejs.org/
"MIT-style license" whatever that means. I guess, any MIT compatible license.

Ajax Autocomplete for Prototype
http://www.devbridge.com/projects/autocomplete/
Same as prototype.js

== plugins

To be added to the list:
* sphinx search
* BackgroundDB

Expand Down
12 changes: 12 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
= INCLUDED LIBRARIES AND PLUGINS

Unless otherwise specified, the license for crabgrass source is the GNU AGPL,
included below.

However, Crabgrass includes many third party libraries and plugins which are not
distributable using the AGPL. This libraries are all covered by free software
licenses, but not the AGPL. For the sake of convenience, these libraries are
included in the standard distribution of Crabgrass. See AUTHORS for a list of
the licensing terms of included software.


= GNU AFFERO GENERAL PUBLIC LICENSE

Version 3, 19 November 2007
Expand Down
Binary file added public/images/ui/shadow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
315 changes: 315 additions & 0 deletions public/javascripts/as_needed/autocomplete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
/*
*
* Ajax Autocomplete for Prototype, version 1.0.3
* (c) 2008 Tomas Kirda
*
* Ajax Autocomplete for Prototype is freely distributable under the terms of an MIT-style license.
* For details, see the web site: http://www.devbridge.com/projects/autocomplete/
*
*/

var Autocomplete = function(el, options, id){
this.el = $(el);
this.id = id ? id : this.el.identify();
this.el.setAttribute('autocomplete','off');
this.suggestions = [];
this.data = [];
this.badQueries = [];
this.selectedIndex = -1;
this.currentValue = this.el.value;
this.intervalId = 0;
this.cachedResponse = [];
this.instanceId = null;
this.onChangeInterval = null;
this.ignoreValueChange = false;
this.serviceUrl = options.serviceUrl;
this.options = {
autoSubmit:false,
minChars:1,
maxHeight:300,
deferRequestBy:0,
width:0,
container:null
};
if(options){ Object.extend(this.options, options); }
if(Autocomplete.isDomLoaded){
this.initialize();
}else{
Event.observe(document, 'dom:loaded', this.initialize.bind(this), false);
}
};

Autocomplete.instances = [];
Autocomplete.isDomLoaded = false;

Autocomplete.getInstance = function(id){
var instances = Autocomplete.instances;
var i = instances.length;
while(i--){ if(instances[i].id === id){ return instances[i]; }}
};

Autocomplete.highlight = function(value, re){
return value.replace(re, function(match){ return '<strong>' + match + '<\/strong>' });
};

Autocomplete.prototype = {

killerFn: null,

initialize: function() {
var me = this;
this.killerFn = function(e) {
if (!$(Event.element(e)).up('.autocomplete')) {
me.killSuggestions();
me.disableKillerFn();
}
} .bindAsEventListener(this);

if (!this.options.width) { this.options.width = this.el.getWidth(); }

var div = new Element('div', { style: 'position:absolute;' });
div.update('<div class="autocomplete-w1"><div class="autocomplete-w2"><div class="autocomplete" id="Autocomplete_' + this.id + '" style="display:none; width:' + this.options.width + 'px;"></div></div></div>');

this.options.container = $(this.options.container);
if (this.options.container) {
this.options.container.appendChild(div);
this.fixPosition = function() { };
} else {
document.body.appendChild(div);
}

this.mainContainerId = div.identify();
this.container = $('Autocomplete_' + this.id);
this.fixPosition();

Event.observe(this.el, window.opera ? 'keypress':'keydown', this.onKeyPress.bind(this));
Event.observe(this.el, 'keyup', this.onKeyUp.bind(this));
Event.observe(this.el, 'blur', this.enableKillerFn.bind(this));
Event.observe(this.el, 'focus', this.fixPosition.bind(this));
this.container.setStyle({ maxHeight: this.options.maxHeight + 'px' });
this.instanceId = Autocomplete.instances.push(this) - 1;
},

fixPosition: function() {
var offset = this.el.cumulativeOffset();
$(this.mainContainerId).setStyle({ top: (offset.top + this.el.getHeight()) + 'px', left: offset.left + 'px' });
},

enableKillerFn: function() {
Event.observe(document.body, 'click', this.killerFn);
},

disableKillerFn: function() {
Event.stopObserving(document.body, 'click', this.killerFn);
},

killSuggestions: function() {
this.stopKillSuggestions();
this.intervalId = window.setInterval(function() { this.hide(); this.stopKillSuggestions(); } .bind(this), 300);
},

stopKillSuggestions: function() {
window.clearInterval(this.intervalId);
},

onKeyPress: function(e) {
if (!this.enabled) { return; }
// return will exit the function
// and event will not fire
switch (e.keyCode) {
case Event.KEY_ESC:
this.el.value = this.currentValue;
this.hide();
break;
case Event.KEY_TAB:
case Event.KEY_RETURN:
if (this.selectedIndex === -1) {
this.hide();
return;
}
this.select(this.selectedIndex);
if (e.keyCode === Event.KEY_TAB) { return; }
break;
case Event.KEY_UP:
this.moveUp();
break;
case Event.KEY_DOWN:
this.moveDown();
break;
default:
return;
}
Event.stop(e);
},

onKeyUp: function(e) {
switch (e.keyCode) {
case Event.KEY_UP:
case Event.KEY_DOWN:
return;
}
clearInterval(this.onChangeInterval);
if (this.currentValue !== this.el.value) {
if (this.options.deferRequestBy > 0) {
// Defer lookup in case when value changes very quickly:
this.onChangeInterval = setInterval((function() {
this.onValueChange();
}).bind(this), this.options.deferRequestBy);
} else {
this.onValueChange();
}
}
},

onValueChange: function() {
clearInterval(this.onChangeInterval);
this.currentValue = this.el.value;
this.selectedIndex = -1;
if (this.ignoreValueChange) {
this.ignoreValueChange = false;
return;
}
if (this.currentValue === '' || this.currentValue.length < this.options.minChars) {
this.hide();
} else {
this.getSuggestions();
}
},

getSuggestions: function() {
var cr = this.cachedResponse[this.currentValue];
if (cr && Object.isArray(cr.suggestions)) {
this.suggestions = cr.suggestions;
this.data = cr.data;
this.suggest();
} else if (!this.isBadQuery(this.currentValue)) {
new Ajax.Request(this.serviceUrl, {
parameters: { query: this.currentValue },
onComplete: this.processResponse.bind(this),
method: 'get'
});
}
},

isBadQuery: function(q) {
var i = this.badQueries.length;
while (i--) {
if (q.indexOf(this.badQueries[i]) === 0) { return true; }
}
return false;
},

hide: function() {
this.enabled = false;
this.selectedIndex = -1;
this.container.hide();
},

suggest: function() {
if (this.suggestions.length === 0) {
this.hide();
return;
}
var content = [];
var re = new RegExp('\\b' + this.currentValue.match(/\w+/g).join('|\\b'), 'gi');
this.suggestions.each(function(value, i) {
content.push((this.selectedIndex === i ? '<div class="selected"' : '<div'),
' onclick="Autocomplete.instances[', this.instanceId, '].select(', i, ');" onmouseover="Autocomplete.instances[', this.instanceId, '].activate(', i, ');">', this.renderRow(value, re, this.data[i]), '</div>');
} .bind(this));
this.enabled = true;
this.container.update(content.join('')).show();
},

processResponse: function(xhr) {
var response;
try {
response = xhr.responseText.evalJSON();
if (!Object.isArray(response.data)) { response.data = []; }
} catch (err) { return; }
this.suggestions = response.suggestions;
this.data = response.data;
this.cachedResponse[response.query] = response;
if (response.suggestions.length === 0) { this.badQueries.push(response.query); }
if (response.query === this.currentValue) { this.suggest(); }
},

activate: function(index) {
var divs = this.container.childNodes;
var activeItem;
// Clear previous selection:
if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) {
divs[this.selectedIndex].className = '';
}
this.selectedIndex = index;
if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) {
activeItem = divs[this.selectedIndex]
activeItem.className = 'selected';
}
return activeItem;
},

deactivate: function(div, index) {
div.className = '';
if (this.selectedIndex === index) { this.selectedIndex = -1; }
},

select: function(i) {
var selectedValue = this.suggestions[i];
if (selectedValue) {
this.el.value = this.selectValue(selectedValue);
if (this.options.autoSubmit && this.el.form) {
this.el.form.submit();
}
this.ignoreValueChange = true;
this.hide();
this.onSelect(i);
}
},

moveUp: function() {
if (this.selectedIndex === -1) { return; }
if (this.selectedIndex === 0) {
this.container.childNodes[0].className = '';
this.selectedIndex = -1;
this.el.value = this.currentValue;
return;
}
this.adjustScroll(this.selectedIndex - 1);
},

moveDown: function() {
if (this.selectedIndex === (this.suggestions.length - 1)) { return; }
this.adjustScroll(this.selectedIndex + 1);
},

adjustScroll: function(i) {
var container = this.container;
var activeItem = this.activate(i);
var offsetTop = activeItem.offsetTop;
var upperBound = container.scrollTop;
var lowerBound = upperBound + this.options.maxHeight - 25;
if (offsetTop < upperBound) {
container.scrollTop = offsetTop;
} else if (offsetTop > lowerBound) {
container.scrollTop = offsetTop - this.options.maxHeight + 25;
}
this.el.value = this.selectValue(this.suggestions[i]);
},

onSelect: function(i) {
(this.options.onSelect || Prototype.emptyFunction)(this.suggestions[i], this.data[i]);
},

// added crabgrass hack: allows custom row rendering
renderRow: function(value, re, data) {
return (this.options.rowRenderer || Autocomplete.highlight)(value, re, data);
},

// added crabgrass hack: allows regexp filter of selected value
selectValue: function(value) {
return (this.options.selectValue ? this.options.selectValue(value) : value);
}

};

Event.observe(document, 'dom:loaded', function(){ Autocomplete.isDomLoaded = true; }, false);
4 changes: 4 additions & 0 deletions public/stylesheets/ie/ie6.css
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,7 @@ div.edit-tab span {

/* this hack makes the access icons appear */
ul.names li span {border: 1px solid #d6d6d6;}

/* autocomplete */
.autocomplete-w1 {background: none; top: 1px;}
.autocomplete {height: 350px; margin: 0px 6px 6px 0;}
4 changes: 3 additions & 1 deletion public/stylesheets/ie/ie7.css
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,6 @@ div.page_list {width: 100%;} /* this is required to get ie7 to actually show scr
}
*/


/* autocomplete */
.autocomplete-w1 {background: none; top: 1px;}
.autocomplete {height: 350px; margin: 0px 6px 6px 0;}
Loading

0 comments on commit 25d8667

Please sign in to comment.