check the css and JS here:
<div class="auto_com plete" id="keywords_au to_complete" style="position : absolute; left: 0px; top: 48px; width: 387px; display: none;"></div>
JS:
// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
// Copyright (c) 2005-2009 Thomas Fuchs (
http://script.aculo.us,
http://mir.aculo.us)
// (c) 2005-2009 Ivan Krstic (
http://blogs.law.harvard.edu/ivan)
// (c) 2005-2009 Jon Tirsen (
http://www.tirsen.com)
// Contributors:
// Richard Livsey
// Rahul Bhargava
// Rob Wills
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site:
http://script.aculo.us/
// Autocompleter.B ase handles all the autocompletion functionality
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least,
// a getUpdatedChoic es function that will be invoked every time
// the text inside the monitored textbox changes. This method
// should get the text for which to provide autocompletion by
// invoking this.getToken() , NOT by directly accessing
// this.element.va lue. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoic es.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocomple ter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most
// useful when one of the tokens is \n (a newline), as it
// allows smart autocompletion after linebreaks.
if(typeof Effect == 'undefined')
throw("controls .js requires including script.aculo.us ' effects.js library");
var Autocompleter = { };
Autocompleter.B ase = Class.create({
baseInitialize: function(elemen t, update, options) {
element = $(element);
this.element = element;
this.update = $(update);
this.hasFocus = false;
this.changed = false;
this.active = false;
this.index = 0;
this.entryCount = 0;
this.oldElement Value = this.element.va lue;
if(this.setOpti ons)
this.setOptions (options);
else
this.options = options || { };
this.options.pa ramName = this.options.pa ramName || this.element.na me;
this.options.to kens = this.options.to kens || [];
this.options.fr equency = this.options.fr equency || 0.4;
this.options.mi nChars = this.options.mi nChars || 1;
this.options.on Show = this.options.on Show ||
function(elemen t, update){
if(!update.styl e.position || update.style.po sition=='absolu te') {
update.style.po sition = 'absolute';
Position.clone( element, update, {
setHeight: false,
offsetTop: element.offsetH eight
});
}
Effect.Appear(u pdate,{duration :0.15});
};
this.options.on Hide = this.options.on Hide ||
function(elemen t, update){ new Effect.Fade(upd ate,{duration:0 .15}) };
if(typeof(this. options.tokens) == 'string')
this.options.to kens = new Array(this.opti ons.tokens);
// Force carriage returns as token delimiters anyway
if (!this.options. tokens.include( '\n'))
this.options.to kens.push('\n') ;
this.observer = null;
this.element.se tAttribute('aut ocomplete','off ');
Element.hide(th is.update);
Event.observe(t his.element, 'blur', this.onBlur.bin dAsEventListene r(this));
Event.observe(t his.element, 'keydown', this.onKeyPress .bindAsEventLis tener(this));
},
show: function() {
if(Element.getS tyle(this.updat e, 'display')=='no ne') this.options.on Show(this.eleme nt, this.update);
if(!this.iefix &&
(Prototype.Brow ser.IE) &&
(Element.getSty le(this.update, 'position')=='a bsolute')) {
new Insertion.After (this.update,
'<iframe id="' + this.update.id + '_iefix" '+
'style="display :none;position: absolute;filter :progid:DXImage Transform.Micro soft.Alpha(opac ity=0);" ' +
'src="javascrip t:false;" frameborder="0" scrolling="no"> </iframe>');
this.iefix = $(this.update.i d+'_iefix');
}
if(this.iefix) setTimeout(this .fixIEOverlappi ng.bind(this), 50);
},
fixIEOverlappin g: function() {
Position.clone( this.update, this.iefix, {setTop:(!this. update.style.he ight)});
this.iefix.styl e.zIndex = 1;
this.update.sty le.zIndex = 2;
Element.show(th is.iefix);
},
hide: function() {
this.stopIndica tor();
if(Element.getS tyle(this.updat e, 'display')!='no ne') this.options.on Hide(this.eleme nt, this.update);
if(this.iefix) Element.hide(th is.iefix);
},
startIndicator: function() {
if(this.options .indicator) Element.show(th is.options.indi cator);
},
stopIndicator: function() {
if(this.options .indicator) Element.hide(th is.options.indi cator);
},
onKeyPress: function(event) {
if(this.active)
switch(event.ke yCode) {
case Event.KEY_TAB:
case Event.KEY_RETUR N:
this.selectEntr y();
Event.stop(even t);
case Event.KEY_ESC:
this.hide();
this.active = false;
Event.stop(even t);
return;
case Event.KEY_LEFT:
case Event.KEY_RIGHT :
return;
case Event.KEY_UP:
this.markPrevio us();
this.render();
Event.stop(even t);
return;
case Event.KEY_DOWN:
this.markNext() ;
this.render();
Event.stop(even t);
return;
}
else
if(event.keyCod e==Event.KEY_TA B || event.keyCode== Event.KEY_RETUR N ||
(Prototype.Brow ser.WebKit > 0 && event.keyCode == 0)) return;
this.changed = true;
this.hasFocus = true;
if(this.observe r) clearTimeout(th is.observer);
this.observer =
setTimeout(this .onObserverEven t.bind(this), this.options.fr equency*1000);
},
activate: function() {
this.changed = false;
this.hasFocus = true;
this.getUpdated Choices();
},
onHover: function(event) {
var element = Event.findEleme nt(event, 'LI');
if(this.index != element.autocom pleteIndex)
{
this.index = element.autocom pleteIndex;
this.render();
}
Event.stop(even t);
},
onClick: function(event) {
var element = Event.findEleme nt(event, 'LI');
this.index = element.autocom pleteIndex;
this.selectEntr y();
this.hide();
},
onBlur: function(event) {
// needed to make click events working
setTimeout(this .hide.bind(this ), 250);
this.hasFocus = false;
this.active = false;
},
render: function() {
if(this.entryCo unt > 0) {
for (var i = 0; i < this.entryCount ; i++)
this.index==i ?
Element.addClas sName(this.getE ntry(i),"select ed") :
Element.removeC lassName(this.g etEntry(i),"sel ected");
if(this.hasFocu s) {
this.show();
this.active = true;
}
} else {
this.active = false;
this.hide();
}
},
markPrevious: function() {
if(this.index > 0) this.index--;
else this.index = this.entryCount-1;
this.getEntry(t his.index).scro llIntoView(true );
},
markNext: function() {
if(this.index < this.entryCount-1) this.index++;
else this.index = 0;
this.getEntry(t his.index).scro llIntoView(fals e);
},
getEntry: function(index) {
return this.update.fir stChild.childNo des[index];
},
getCurrentEntry : function() {
return this.getEntry(t his.index);
},
selectEntry: function() {
this.active = false;
this.updateElem ent(this.getCur rentEntry());
},
updateElement: function(select edElement) {
if (this.options.u pdateElement) {
this.options.up dateElement(sel ectedElement);
return;
}
var value = '';
if (this.options.s elect) {
var nodes = $(selectedEleme nt).select('.' + this.options.se lect) || [];
if(nodes.length >0) value = Element.collect TextNodes(nodes[0], this.options.se lect);
} else
value = Element.collect TextNodesIgnore Class(selectedE lement, 'informal');
var bounds = this.getTokenBo unds();
if (bounds[0] != -1) {
var newValue = this.element.va lue.substr(0, bounds[0]);
var whitespace = this.element.va lue.substr(boun ds[0]).match(/^\s+/);
if (whitespace)
newValue += whitespace[0];
this.element.va lue = newValue + value + this.element.va lue.substr(boun ds[1]);
} else {
this.element.va lue = value;
}
this.oldElement Value = this.element.va lue;
this.element.fo cus();
if (this.options.a fterUpdateEleme nt)
this.options.af terUpdateElemen t(this.element, selectedElement );
},
updateChoices: function(choice s) {
if(!this.change d && this.hasFocus) {
this.update.inn erHTML = choices;
Element.cleanWh itespace(this.u pdate);
Element.cleanWh itespace(this.u pdate.down());
if(this.update. firstChild && this.update.dow n().childNodes) {
this.entryCount =
this.update.dow n().childNodes. length;
for (var i = 0; i < this.entryCount ; i++) {
var entry = this.getEntry(i );
entry.autocompl eteIndex = i;
this.addObserve rs(entry);
}
} else {
this.entryCount = 0;
}
this.stopIndica tor();
this.index = 0;
if(this.entryCo unt==1 && this.options.au toSelect) {
this.selectEntr y();
this.hide();
} else {
this.render();
}
}
},
addObservers: function(elemen t) {
Event.observe(e lement, "mouseover" , this.onHover.bi ndAsEventListen er(this));
Event.observe(e lement, "click", this.onClick.bi ndAsEventListen er(this));
},
onObserverEvent : function() {
this.changed = false;
this.tokenBound s = null;
if(this.getToke n().length>=thi s.options.minCh ars) {
this.getUpdated Choices();
} else {
this.active = false;
this.hide();
}
this.oldElement Value = this.element.va lue;
},
getToken: function() {
var bounds = this.getTokenBo unds();
return this.element.va lue.substring(b ounds[0], bounds[1]).strip();
},
getTokenBounds: function() {
if (null != this.tokenBound s) return this.tokenBound s;
var value = this.element.va lue;
if (value.strip(). empty()) return [-1, 0];
var diff = arguments.calle e.getFirstDiffe rencePos(value, this.oldElement Value);
var offset = (diff == this.oldElement Value.length ? 1 : 0);
var prevTokenPos = -1, nextTokenPos = value.length;
var tp;
for (var index = 0, l = this.options.to kens.length; index < l; ++index) {
tp = value.lastIndex Of(this.options .tokens[index], diff + offset - 1);
if (tp > prevTokenPos) prevTokenPos = tp;
tp = value.indexOf(t his.options.tok ens[index], diff + offset);
if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
}
return (this.tokenBoun ds = [prevTokenPos + 1, nextTokenPos]);
}
});
Autocompleter.B ase.prototype.g etTokenBounds.g etFirstDifferen cePos = function(newS, oldS) {
var boundary = Math.min(newS.l ength, oldS.length);
for (var index = 0; index < boundary; ++index)
if (newS[index] != oldS[index])
return index;
return boundary;
};
Ajax.Autocomple ter = Class.create(Au tocompleter.Bas e, {
initialize: function(elemen t, update, url, options) {
this.baseInitia lize(element, update, options);
this.options.as ynchronous = true;
this.options.on Complete = this.onComplete .bind(this);
this.options.de faultParams = this.options.pa rameters || null;
this.url = url;
},
getUpdatedChoic es: function() {
this.startIndic ator();
var entry = encodeURICompon ent(this.option s.paramName) + '=' +
encodeURICompon ent(this.getTok en());
this.options.pa rameters = this.options.ca llback ?
this.options.ca llback(this.ele ment, entry) : entry;
if(this.options .defaultParams)
this.options.pa rameters += '&' + this.options.de faultParams;
new Ajax.Request(th is.url, this.options);
},
onComplete: function(reques t) {
this.updateChoi ces(request.res ponseText);
}
});
// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
// text only at the beginning of strings in the
// autocomplete array. Defaults to true, which will
// match text at the beginning of any *word* in the
// strings in the autocomplete array. If you want to
// search anywhere in the string, additionally set
// the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
// a partial match (unlike minChars, which defines
// how many characters are required to do any match
// at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
// Defaults to true.
//
// It's possible to pass in a custom function as the 'selector'
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.
Autocompleter.L ocal = Class.create(Au tocompleter.Bas e, {
initialize: function(elemen t, update, array, options) {
this.baseInitia lize(element, update, options);
this.options.ar ray = array;
},
getUpdatedChoic es: function() {
this.updateChoi ces(this.option s.selector(this ));
},
setOptions: function(option s) {
this.options = Object.extend({
choices: 10,
partialSearch: true,
partialChars: 2,
ignoreCase: true,
fullSearch: false,
selector: function(instan ce) {
var ret = []; // Beginning matches
var partial = []; // Inside matches
var entry = instance.getTok en();
var count = 0;
for (var i = 0; i < instance.option s.array.length &&
ret.length < instance.option s.choices ; i++) {
var elem = instance.option s.array[i];
var foundPos = instance.option s.ignoreCase ?
elem.toLowerCas e().indexOf(ent ry.toLowerCase( )) :
elem.indexOf(en try);
while (foundPos != -1) {
if (foundPos == 0 && elem.length != entry.length) {
ret.push("<li>< strong>" + elem.substr(0, entry.length) + "</strong>" +
elem.substr(ent ry.length) + "</li>");
break;
} else if (entry.length >= instance.option s.partialChars &&
instance.option s.partialSearch && foundPos != -1) {
if (instance.optio ns.fullSearch || /\s/.test(elem.subs tr(foundPos-1,1))) {
partial.push("< li>" + elem.substr(0, foundPos) + "<strong>" +
elem.substr(fou ndPos, entry.length) + "</strong>" + elem.substr(
foundPos + entry.length) + "</li>");
break;
}
}
foundPos = instance.option s.ignoreCase ?
elem.toLowerCas e().indexOf(ent ry.toLowerCase( ), foundPos + 1) :
elem.indexOf(en try, foundPos + 1);
}
}
if (partial.length )
ret = ret.concat(part ial.slice(0, instance.option s.choices - ret.length));
return "<ul>" + ret.join('') + "</ul>";
}
}, options || { });
}
});
// AJAX in-place editor and collection editor
// Full rewrite by Christophe Porteneuve <tdd@tddsworld. com> (April 2007).
// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFre eActivate = function(field) {
setTimeout(func tion() {
Field.activate( field);
}, 1);
};
Ajax.InPlaceEdi tor = Class.create({
initialize: function(elemen t, url, options) {
this.url = url;
this.element = element = $(element);
this.prepareOpt ions();
this._controls = { };
arguments.calle e.dealWithDepre catedOptions(op tions); // DEPRECATION LAYER!!!
Object.extend(t his.options, options || { });
if (!this.options. formId && this.element.id ) {
this.options.fo rmId = this.element.id + '-inplaceeditor';
if ($(this.options .formId))
this.options.fo rmId = '';
}
if (this.options.e xternalControl)
this.options.ex ternalControl = $(this.options. externalControl );
if (!this.options. externalControl )
this.options.ex ternalControlOn ly = false;
this._originalB ackground = this.element.ge tStyle('backgro und-color') || 'transparent';
this.element.ti tle = this.options.cl ickToEditText;
this._boundCanc elHandler = this.handleForm Cancellation.bi nd(this);
this._boundComp lete = (this.options.o nComplete || Prototype.empty Function).bind( this);
this._boundFail ureHandler = this.handleAJAX Failure.bind(th is);
this._boundSubm itHandler = this.handleForm Submission.bind (this);
this._boundWrap perHandler = this.wrapUp.bin d(this);
this.registerLi steners();
},
checkForEscapeO rReturn: function(e) {
if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
if (Event.KEY_ESC == e.keyCode)
this.handleForm Cancellation(e) ;
else if (Event.KEY_RETU RN == e.keyCode)
this.handleForm Submission(e);
},
createControl: function(mode, handler, extraClasses) {
var control = this.options[mode + 'Control'];
var text = this.options[mode + 'Text'];
if ('button' == control) {
var btn = document.create Element('input' );
btn.type = 'submit';
btn.value = text;
btn.className = 'editor_' + mode + '_button';
if ('cancel' == mode)
btn.onclick = this._boundCanc elHandler;
this._form.appe ndChild(btn);
this._controls[mode] = btn;
} else if ('link' == control) {
var link = document.create Element('a');
link.href = '#';
link.appendChil d(document.crea teTextNode(text ));
link.onclick = 'cancel' == mode ? this._boundCanc elHandler : this._boundSubm itHandler;
link.className = 'editor_' + mode + '_link';
if (extraClasses)
link.className += ' ' + extraClasses;
this._form.appe ndChild(link);
this._controls[mode] = link;
}
},
createEditField : function() {
var text = (this.options.l oadTextURL ? this.options.lo adingText : this.getText()) ;
var fld;
if (1 >= this.options.ro ws && !/\r|\n/.test(this.getT ext())) {
fld = document.create Element('input' );
fld.type = 'text';
var size = this.options.si ze || this.options.co ls || 0;
if (0 < size) fld.size = size;
} else {
fld = document.create Element('textar ea');
fld.rows = (1 >= this.options.ro ws ? this.options.au toRows : this.options.ro ws);
fld.cols = this.options.co ls || 40;
}
fld.name = this.options.pa ramName;
fld.value = text; // No HTML breaks conversion anymore
fld.className = 'editor_field';
if (this.options.s ubmitOnBlur)
fld.onblur = this._boundSubm itHandler;
this._controls. editor = fld;
if (this.options.l oadTextURL)
this.loadExtern alText();
this._form.appe ndChild(this._c ontrols.editor) ;
},
createForm: function() {
var ipe = this;
function addText(mode, condition) {
var text = ipe.options['text' + mode + 'Controls'];
if (!text || condition === false) return;
ipe._form.appen dChild(document .createTextNode (text));
};
this._form = $(document.crea teElement('form '));
this._form.id = this.options.fo rmId;
this._form.addC lassName(this.o ptions.formClas sName);
this._form.onsu bmit = this._boundSubm itHandler;
this.createEdit Field();
if ('textarea' == this._controls. editor.tagName. toLowerCase())
this._form.appe ndChild(documen t.createElement ('br'));
if (this.options.o nFormCustomizat ion)
this.options.on FormCustomizati on(this, this._form);
addText('Before ', this.options.ok Control || this.options.ca ncelControl);
this.createCont rol('ok', this._boundSubm itHandler);
addText('Betwee n', this.options.ok Control && this.options.ca ncelControl);
this.createCont rol('cancel', this._boundCanc elHandler, 'editor_cancel' );
addText('After' , this.options.ok Control || this.options.ca ncelControl);
},
destroy: function() {
if (this._oldInner HTML)
this.element.in nerHTML = this._oldInnerH TML;
this.leaveEditM ode();
this.unregister Listeners();
},
enterEditMode: function(e) {
if (this._saving || this._editing) return;
this._editing = true;
this.triggerCal lback('onEnterE ditMode');
if (this.options.e xternalControl)
this.options.ex ternalControl.h ide();
this.element.hi de();
this.createForm ();
this.element.pa rentNode.insert Before(this._fo rm, this.element);
if (!this.options. loadTextURL)
this.postProces sEditField();
if (e) Event.stop(e);
},
enterHover: function(e) {
if (this.options.h overClassName)
this.element.ad dClassName(this .options.hoverC lassName);
if (this._saving) return;
this.triggerCal lback('onEnterH over');
},
getText: function() {
return this.element.in nerHTML.unescap eHTML();
},
handleAJAXFailu re: function(transp ort) {
this.triggerCal lback('onFailur e', transport);
if (this._oldInner HTML) {
this.element.in nerHTML = this._oldInnerH TML;
this._oldInnerH TML = null;
}
},
handleFormCance llation: function(e) {
this.wrapUp();
if (e) Event.stop(e);
},
handleFormSubmi ssion: function(e) {
var form = this._form;
var value = $F(this._contro ls.editor);
this.prepareSub mission();
var params = this.options.ca llback(form, value) || '';
if (Object.isStrin g(params))
params = params.toQueryP arams();
params.editorId = this.element.id ;
if (this.options.h tmlResponse) {
var options = Object.extend({ evalScripts: true }, this.options.aj axOptions);
Object.extend(o ptions, {
parameters: params,
onComplete: this._boundWrap perHandler,
onFailure: this._boundFail ureHandler
});
new Ajax.Updater({ success: this.element }, this.url, options);
} else {
var options = Object.extend({ method: 'get' }, this.options.aj axOptions);
Object.extend(o ptions, {
parameters: params,
onComplete: this._boundWrap perHandler,
onFailure: this._boundFail ureHandler
});
new Ajax.Request(th is.url, options);
}
if (e) Event.stop(e);
},
leaveEditMode: function() {
this.element.re moveClassName(t his.options.sav ingClassName);
this.removeForm ();
this.leaveHover ();
this.element.st yle.backgroundC olor = this._originalB ackground;
this.element.sh ow();
if (this.options.e xternalControl)
this.options.ex ternalControl.s how();
this._saving = false;
this._editing = false;
this._oldInnerH TML = null;
this.triggerCal lback('onLeaveE ditMode');
},
leaveHover: function(e) {
if (this.options.h overClassName)
this.element.re moveClassName(t his.options.hov erClassName);
if (this._saving) return;
this.triggerCal lback('onLeaveH over');
},
loadExternalTex t: function() {
this._form.addC lassName(this.o ptions.loadingC lassName);
this._controls. editor.disabled = true;
var options = Object.extend({ method: 'get' }, this.options.aj axOptions);
Object.extend(o ptions, {
parameters: 'editorId=' + encodeURICompon ent(this.elemen t.id),
onComplete: Prototype.empty Function,
onSuccess: function(transp ort) {
this._form.remo veClassName(thi s.options.loadi ngClassName);
var text = transport.respo nseText;
if (this.options.s tripLoadedTextT ags)
text = text.stripTags( );
this._controls. editor.value = text;
this._controls. editor.disabled = false;
this.postProces sEditField();
}.bind(this),
onFailure: this._boundFail ureHandler
});
new Ajax.Request(th is.options.load TextURL, options);
},
postProcessEdit Field: function() {
var fpc = this.options.fi eldPostCreation ;
if (fpc)
$(this._control s.editor)['focus' == fpc ? 'focus' : 'activate']();
},
prepareOptions: function() {
this.options = Object.clone(Aj ax.InPlaceEdito r.DefaultOption s);
Object.extend(t his.options, Ajax.InPlaceEdi tor.DefaultCall backs);
[this._extraDefa ultOptions].flatten().comp act().each(func tion(defs) {
Object.extend(t his.options, defs);
}.bind(this));
},
prepareSubmissi on: function() {
this._saving = true;
this.removeForm ();
this.leaveHover ();
this.showSaving ();
},
registerListene rs: function() {
this._listeners = { };
var listener;
$H(Ajax.InPlace Editor.Listener s).each(functio n(pair) {
listener = this[pair.value].bind(this);
this._listeners[pair.key] = listener;
if (!this.options. externalControl Only)
this.element.ob serve(pair.key, listener);
if (this.options.e xternalControl)
this.options.ex ternalControl.o bserve(pair.key , listener);
}.bind(this));
},
removeForm: function() {
if (!this._form) return;
this._form.remo ve();
this._form = null;
this._controls = { };
},
showSaving: function() {
this._oldInnerH TML = this.element.in nerHTML;
this.element.in nerHTML = this.options.sa vingText;
this.element.ad dClassName(this .options.saving ClassName);
this.element.st yle.backgroundC olor = this._originalB ackground;
this.element.sh ow();
},
triggerCallback : function(cbName , arg) {
if ('function' == typeof this.options[cbName]) {
this.options[cbName](this, arg);
}
},
unregisterListe ners: function() {
$H(this._listen ers).each(funct ion(pair) {
if (!this.options. externalControl Only)
this.element.st opObserving(pai r.key, pair.value);
if (this.options.e xternalControl)
this.options.ex ternalControl.s topObserving(pa ir.key, pair.value);
}.bind(this));
},
wrapUp: function(transp ort) {
this.leaveEditM ode();
// Can't use triggerCallback due to backward compatibility: requires
// binding + direct element
this._boundComp lete(transport, this.element);
}
});
Object.extend(A jax.InPlaceEdit or.prototype, {
dispose: Ajax.InPlaceEdi tor.prototype.d estroy
});
Ajax.InPlaceCol lectionEditor = Class.create(Aj ax.InPlaceEdito r, {
initialize: function($super , element, url, options) {
this._extraDefa ultOptions = Ajax.InPlaceCol lectionEditor.D efaultOptions;
$super(element, url, options);
},
createEditField : function() {
var list = document.create Element('select ');
list.name = this.options.pa ramName;
list.size = 1;
this._controls. editor = list;
this._collectio n = this.options.co llection || [];
if (this.options.l oadCollectionUR L)
this.loadCollec tion();
else
this.checkForEx ternalText();
this._form.appe ndChild(this._c ontrols.editor) ;
},
loadCollection: function() {
this._form.addC lassName(this.o ptions.loadingC lassName);
this.showLoadin gText(this.opti ons.loadingColl ectionText);
var options = Object.extend({ method: 'get' }, this.options.aj axOptions);
Object.extend(o ptions, {
parameters: 'editorId=' + encodeURICompon ent(this.elemen t.id),
onComplete: Prototype.empty Function,
onSuccess: function(transp ort) {
var js = transport.respo nseText.strip() ;
if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
throw('Server returned an invalid collection representation. ');
this._collectio n = eval(js);
this.checkForEx ternalText();
}.bind(this),
onFailure: this.onFailure
});
new Ajax.Request(th is.options.load CollectionURL, options);
},
showLoadingText : function(text) {
this._controls. editor.disabled = true;
var tempOption = this._controls. editor.firstChi ld;
if (!tempOption) {
tempOption = document.create Element('option ');
tempOption.valu e = '';
this._controls. editor.appendCh ild(tempOption) ;
tempOption.sele cted = true;
}
tempOption.upda te((text || '').stripScript s().stripTags() );
},
checkForExterna lText: function() {
this._text = this.getText();
if (this.options.l oadTextURL)
this.loadExtern alText();
else
this.buildOptio nList();
},
loadExternalTex t: function() {
this.showLoadin gText(this.opti ons.loadingText );
var options = Object.extend({ method: 'get' }, this.options.aj axOptions);
Object.extend(o ptions, {
parameters: 'editorId=' + encodeURICompon ent(this.elemen t.id),
onComplete: Prototype.empty Function,
onSuccess: function(transp ort) {
this._text = transport.respo nseText.strip() ;
this.buildOptio nList();
}.bind(this),
onFailure: this.onFailure
});
new Ajax.Request(th is.options.load TextURL, options);
},
buildOptionList : function() {
this._form.remo veClassName(thi s.options.loadi ngClassName);
this._collectio n = this._collectio n.map(function( entry) {
return 2 === entry.length ? entry : [entry, entry].flatten();
});
var marker = ('value' in this.options) ? this.options.va lue : this._text;
var textFound = this._collectio n.any(function( entry) {
return entry[0] == marker;
}.bind(this));
this._controls. editor.update(' ');
var option;
this._collectio n.each(function (entry, index) {
option = document.create Element('option ');
option.value = entry[0];
option.selected = textFound ? entry[0] == marker : 0 == index;
option.appendCh ild(document.cr eateTextNode(en try[1]));
this._controls. editor.appendCh ild(option);
}.bind(this));
this._controls. editor.disabled = false;
Field.scrollFre eActivate(this. _controls.edito r);
}
});
//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
//**** This only exists for a while, in order to let ****
//**** users adapt to the new API. Read up on the new ****
//**** API and convert your code to it ASAP! ****
Ajax.InPlaceEdi tor.prototype.i nitialize.dealW ithDeprecatedOp tions = function(option s) {
if (!options) return;
function fallback(name, expr) {
if (name in options || expr === undefined) return;
options[name] = expr;
};
fallback('cance lControl', (options.cancel Link ? 'link' : (options.cancel Button ? 'button' :
options.cancelL ink == options.cancelB utton == false ? false : undefined)));
fallback('okCon trol', (options.okLink ? 'link' : (options.okButt on ? 'button' :
options.okLink == options.okButto n == false ? false : undefined)));
fallback('highl ightColor', options.highlig htcolor);
fallback('highl ightEndColor', options.highlig htendcolor);
};
Object.extend(A jax.InPlaceEdit or, {
DefaultOptions: {
ajaxOptions: { },
autoRows: 3, // Use when multi-line w/ rows == 1
cancelControl: 'link', // 'link'|'button' |false
cancelText: 'cancel',
clickToEditText : 'Click to edit',
externalControl : null, // id|elt
externalControl Only: false,
fieldPostCreati on: 'activate', // 'activate'|'foc us'|false
formClassName: 'inplaceeditor-form',
formId: null, // id|elt
highlightColor: '#ffff99',
highlightEndCol or: '#ffffff',
hoverClassName: '',
htmlResponse: true,
loadingClassNam e: 'inplaceeditor-loading',
loadingText: 'Loading...',
okControl: 'button', // 'link'|'button' |false
okText: 'ok',
paramName: 'value',
rows: 1, // If 1 and multi-line, uses autoRows
savingClassName : 'inplaceeditor-saving',
savingText: 'Saving...',
size: 0,
stripLoadedText Tags: false,
submitOnBlur: false,
textAfterContro ls: '',
textBeforeContr ols: '',
textBetweenCont rols: ''
},
DefaultCallback s: {
callback: function(form) {
return Form.serialize( form);
},
onComplete: function(transp ort, element) {
// For backward compatibility, this one is bound to the IPE, and passes
// the element directly. It was too often customized, so we don't break it.
new Effect.Highligh t(element, {
startcolor: this.options.hi ghlightColor, keepBackgroundI mage: true });
},
onEnterEditMode : null,
onEnterHover: function(ipe) {
ipe.element.sty le.backgroundCo lor = ipe.options.hig hlightColor;
if (ipe._effect)
ipe._effect.can cel();
},
onFailure: function(transp ort, ipe) {
alert('Error communication with the server: ' + transport.respo nseText.stripTa gs());
},
onFormCustomiza tion: null, // Takes the IPE and its generated form, after editor, before controls.
onLeaveEditMode : null,
onLeaveHover: function(ipe) {
ipe._effect = new Effect.Highligh t(ipe.element, {
startcolor: ipe.options.hig hlightColor, endcolor: ipe.options.hig hlightEndColor,
restorecolor: ipe._originalBa ckground, keepBackgroundI mage: true
});
}
},
Listeners: {
click: 'enterEditMode' ,
keydown: 'checkForEscape OrReturn',
mouseover: 'enterHover',
mouseout: 'leaveHover'
}
});
Ajax.InPlaceCol lectionEditor.D efaultOptions = {
loadingCollecti onText: 'Loading options...'
};
// Delayed observer, like Form.Element.Ob server,
// but waits for delay after last key input
// Ideal for live-search fields
Form.Element.De layedObserver = Class.create({
initialize: function(elemen t, delay, callback) {
this.delay = delay || 0.5;
this.element = $(element);
this.callback = callback;
this.timer = null;
this.lastValue = $F(this.element );
Event.observe(t his.element,'ke yup',this.delay edListener.bind AsEventListener (this));
},
delayedListener : function(event) {
if(this.lastVal ue == $F(this.element )) return;
if(this.timer) clearTimeout(th is.timer);
this.timer = setTimeout(this .onTimerEvent.b ind(this), this.delay * 1000);
this.lastValue = $F(this.element );
},
onTimerEvent: function() {
this.timer = null;
this.callback(t his.element, $F(this.element ));
}
});