123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389 |
- /***
- Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
- (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
- (c) 2005 Jon Tirsen (http://www.tirsen.com)
- Contributors:
- Richard Livsey
- Rahul Bhargava
- Rob Wills
- Mochi-ized By Thomas Herve (_firstname_@nimail.org)
- See scriptaculous.js for full license.
- Autocompleter.Base 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 getUpdatedChoices 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.value. This is to allow incremental tokenized
- autocompletion. Specific auto-completion logic (AJAX, etc)
- belongs in getUpdatedChoices.
- Tokenized incremental autocompletion is enabled automatically
- when an autocompleter is instantiated with the 'tokens' option
- in the options parameter, e.g.:
- new Ajax.Autocompleter('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.
- ***/
- MochiKit.Base.update(MochiKit.Base, {
- ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
- /** @id MochiKit.Base.stripScripts */
- stripScripts: function (str) {
- return str.replace(new RegExp(MochiKit.Base.ScriptFragment, 'img'), '');
- },
- /** @id MochiKit.Base.stripTags */
- stripTags: function(str) {
- return str.replace(/<\/?[^>]+>/gi, '');
- },
- /** @id MochiKit.Base.extractScripts */
- extractScripts: function (str) {
- var matchAll = new RegExp(MochiKit.Base.ScriptFragment, 'img');
- var matchOne = new RegExp(MochiKit.Base.ScriptFragment, 'im');
- return MochiKit.Base.map(function (scriptTag) {
- return (scriptTag.match(matchOne) || ['', ''])[1];
- }, str.match(matchAll) || []);
- },
- /** @id MochiKit.Base.evalScripts */
- evalScripts: function (str) {
- return MochiKit.Base.map(function (scr) {
- eval(scr);
- }, MochiKit.Base.extractScripts(str));
- }
- });
- MochiKit.Form = {
- /** @id MochiKit.Form.serialize */
- serialize: function (form) {
- var elements = MochiKit.Form.getElements(form);
- var queryComponents = [];
- for (var i = 0; i < elements.length; i++) {
- var queryComponent = MochiKit.Form.serializeElement(elements[i]);
- if (queryComponent) {
- queryComponents.push(queryComponent);
- }
- }
- return queryComponents.join('&');
- },
- /** @id MochiKit.Form.getElements */
- getElements: function (form) {
- form = MochiKit.DOM.getElement(form);
- var elements = [];
- for (tagName in MochiKit.Form.Serializers) {
- var tagElements = form.getElementsByTagName(tagName);
- for (var j = 0; j < tagElements.length; j++) {
- elements.push(tagElements[j]);
- }
- }
- return elements;
- },
- /** @id MochiKit.Form.serializeElement */
- serializeElement: function (element) {
- element = MochiKit.DOM.getElement(element);
- var method = element.tagName.toLowerCase();
- var parameter = MochiKit.Form.Serializers[method](element);
- if (parameter) {
- var key = encodeURIComponent(parameter[0]);
- if (key.length === 0) {
- return;
- }
- if (!(parameter[1] instanceof Array)) {
- parameter[1] = [parameter[1]];
- }
- return parameter[1].map(function (value) {
- return key + '=' + encodeURIComponent(value);
- }).join('&');
- }
- }
- };
- MochiKit.Form.Serializers = {
- /** @id MochiKit.Form.Serializers.input */
- input: function (element) {
- switch (element.type.toLowerCase()) {
- case 'submit':
- case 'hidden':
- case 'password':
- case 'text':
- return MochiKit.Form.Serializers.textarea(element);
- case 'checkbox':
- case 'radio':
- return MochiKit.Form.Serializers.inputSelector(element);
- }
- return false;
- },
- /** @id MochiKit.Form.Serializers.inputSelector */
- inputSelector: function (element) {
- if (element.checked) {
- return [element.name, element.value];
- }
- },
- /** @id MochiKit.Form.Serializers.textarea */
- textarea: function (element) {
- return [element.name, element.value];
- },
- /** @id MochiKit.Form.Serializers.select */
- select: function (element) {
- return MochiKit.Form.Serializers[element.type == 'select-one' ?
- 'selectOne' : 'selectMany'](element);
- },
- /** @id MochiKit.Form.Serializers.selectOne */
- selectOne: function (element) {
- var value = '', opt, index = element.selectedIndex;
- if (index >= 0) {
- opt = element.options[index];
- value = opt.value;
- if (!value && !('value' in opt)) {
- value = opt.text;
- }
- }
- return [element.name, value];
- },
- /** @id MochiKit.Form.Serializers.selectMany */
- selectMany: function (element) {
- var value = [];
- for (var i = 0; i < element.length; i++) {
- var opt = element.options[i];
- if (opt.selected) {
- var optValue = opt.value;
- if (!optValue && !('value' in opt)) {
- optValue = opt.text;
- }
- value.push(optValue);
- }
- }
- return [element.name, value];
- }
- };
- /** @id Ajax */
- var Ajax = {
- activeRequestCount: 0
- };
- Ajax.Responders = {
- responders: [],
- /** @id Ajax.Responders.register */
- register: function (responderToAdd) {
- if (MochiKit.Base.find(this.responders, responderToAdd) == -1) {
- this.responders.push(responderToAdd);
- }
- },
- /** @id Ajax.Responders.unregister */
- unregister: function (responderToRemove) {
- this.responders = this.responders.without(responderToRemove);
- },
- /** @id Ajax.Responders.dispatch */
- dispatch: function (callback, request, transport, json) {
- MochiKit.Iter.forEach(this.responders, function (responder) {
- if (responder[callback] &&
- typeof(responder[callback]) == 'function') {
- try {
- responder[callback].apply(responder, [request, transport, json]);
- } catch (e) {}
- }
- });
- }
- };
- Ajax.Responders.register({
- /** @id Ajax.Responders.onCreate */
- onCreate: function () {
- Ajax.activeRequestCount++;
- },
- /** @id Ajax.Responders.onComplete */
- onComplete: function () {
- Ajax.activeRequestCount--;
- }
- });
- /** @id Ajax.Base */
- Ajax.Base = function () {};
- Ajax.Base.prototype = {
- /** @id Ajax.Base.prototype.setOptions */
- setOptions: function (options) {
- this.options = {
- method: 'post',
- asynchronous: true,
- parameters: ''
- }
- MochiKit.Base.update(this.options, options || {});
- },
- /** @id Ajax.Base.prototype.responseIsSuccess */
- responseIsSuccess: function () {
- return this.transport.status == undefined
- || this.transport.status === 0
- || (this.transport.status >= 200 && this.transport.status < 300);
- },
- /** @id Ajax.Base.prototype.responseIsFailure */
- responseIsFailure: function () {
- return !this.responseIsSuccess();
- }
- };
- /** @id Ajax.Request */
- Ajax.Request = function (url, options) {
- this.__init__(url, options);
- };
- /** @id Ajax.Events */
- Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded',
- 'Interactive', 'Complete'];
- MochiKit.Base.update(Ajax.Request.prototype, Ajax.Base.prototype);
- MochiKit.Base.update(Ajax.Request.prototype, {
- __init__: function (url, options) {
- this.transport = MochiKit.Async.getXMLHttpRequest();
- this.setOptions(options);
- this.request(url);
- },
- /** @id Ajax.Request.prototype.request */
- request: function (url) {
- var parameters = this.options.parameters || '';
- if (parameters.length > 0){
- parameters += '&_=';
- }
- try {
- this.url = url;
- if (this.options.method == 'get' && parameters.length > 0) {
- this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
- }
- Ajax.Responders.dispatch('onCreate', this, this.transport);
- this.transport.open(this.options.method, this.url,
- this.options.asynchronous);
- if (this.options.asynchronous) {
- this.transport.onreadystatechange = MochiKit.Base.bind(this.onStateChange, this);
- setTimeout(MochiKit.Base.bind(function () {
- this.respondToReadyState(1);
- }, this), 10);
- }
- this.setRequestHeaders();
- var body = this.options.postBody ? this.options.postBody : parameters;
- this.transport.send(this.options.method == 'post' ? body : null);
- } catch (e) {
- this.dispatchException(e);
- }
- },
- /** @id Ajax.Request.prototype.setRequestHeaders */
- setRequestHeaders: function () {
- var requestHeaders = ['X-Requested-With', 'XMLHttpRequest'];
- if (this.options.method == 'post') {
- requestHeaders.push('Content-type',
- 'application/x-www-form-urlencoded');
- /* Force 'Connection: close' for Mozilla browsers to work around
- * a bug where XMLHttpRequest sends an incorrect Content-length
- * header. See Mozilla Bugzilla #246651.
- */
- if (this.transport.overrideMimeType) {
- requestHeaders.push('Connection', 'close');
- }
- }
- if (this.options.requestHeaders) {
- requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
- }
- for (var i = 0; i < requestHeaders.length; i += 2) {
- this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
- }
- },
- /** @id Ajax.Request.prototype.onStateChange */
- onStateChange: function () {
- var readyState = this.transport.readyState;
- if (readyState != 1) {
- this.respondToReadyState(this.transport.readyState);
- }
- },
- /** @id Ajax.Request.prototype.header */
- header: function (name) {
- try {
- return this.transport.getResponseHeader(name);
- } catch (e) {}
- },
- /** @id Ajax.Request.prototype.evalJSON */
- evalJSON: function () {
- try {
- return eval(this.header('X-JSON'));
- } catch (e) {}
- },
- /** @id Ajax.Request.prototype.evalResponse */
- evalResponse: function () {
- try {
- return eval(this.transport.responseText);
- } catch (e) {
- this.dispatchException(e);
- }
- },
- /** @id Ajax.Request.prototype.respondToReadyState */
- respondToReadyState: function (readyState) {
- var event = Ajax.Request.Events[readyState];
- var transport = this.transport, json = this.evalJSON();
- if (event == 'Complete') {
- try {
- (this.options['on' + this.transport.status]
- || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
- || MochiKit.Base.noop)(transport, json);
- } catch (e) {
- this.dispatchException(e);
- }
- if ((this.header('Content-type') || '').match(/^text\/javascript/i)) {
- this.evalResponse();
- }
- }
- try {
- (this.options['on' + event] || MochiKit.Base.noop)(transport, json);
- Ajax.Responders.dispatch('on' + event, this, transport, json);
- } catch (e) {
- this.dispatchException(e);
- }
- /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
- if (event == 'Complete') {
- this.transport.onreadystatechange = MochiKit.Base.noop;
- }
- },
- /** @id Ajax.Request.prototype.dispatchException */
- dispatchException: function (exception) {
- (this.options.onException || MochiKit.Base.noop)(this, exception);
- Ajax.Responders.dispatch('onException', this, exception);
- }
- });
- /** @id Ajax.Updater */
- Ajax.Updater = function (container, url, options) {
- this.__init__(container, url, options);
- };
- MochiKit.Base.update(Ajax.Updater.prototype, Ajax.Request.prototype);
- MochiKit.Base.update(Ajax.Updater.prototype, {
- __init__: function (container, url, options) {
- this.containers = {
- success: container.success ? MochiKit.DOM.getElement(container.success) : MochiKit.DOM.getElement(container),
- failure: container.failure ? MochiKit.DOM.getElement(container.failure) :
- (container.success ? null : MochiKit.DOM.getElement(container))
- }
- this.transport = MochiKit.Async.getXMLHttpRequest();
- this.setOptions(options);
- var onComplete = this.options.onComplete || MochiKit.Base.noop;
- this.options.onComplete = MochiKit.Base.bind(function (transport, object) {
- this.updateContent();
- onComplete(transport, object);
- }, this);
- this.request(url);
- },
- /** @id Ajax.Updater.prototype.updateContent */
- updateContent: function () {
- var receiver = this.responseIsSuccess() ?
- this.containers.success : this.containers.failure;
- var response = this.transport.responseText;
- if (!this.options.evalScripts) {
- response = MochiKit.Base.stripScripts(response);
- }
- if (receiver) {
- if (this.options.insertion) {
- new this.options.insertion(receiver, response);
- } else {
- MochiKit.DOM.getElement(receiver).innerHTML =
- MochiKit.Base.stripScripts(response);
- setTimeout(function () {
- MochiKit.Base.evalScripts(response);
- }, 10);
- }
- }
- if (this.responseIsSuccess()) {
- if (this.onComplete) {
- setTimeout(MochiKit.Base.bind(this.onComplete, this), 10);
- }
- }
- }
- });
- /** @id Field */
- var Field = {
- /** @id clear */
- clear: function () {
- for (var i = 0; i < arguments.length; i++) {
- MochiKit.DOM.getElement(arguments[i]).value = '';
- }
- },
- /** @id focus */
- focus: function (element) {
- MochiKit.DOM.getElement(element).focus();
- },
- /** @id present */
- present: function () {
- for (var i = 0; i < arguments.length; i++) {
- if (MochiKit.DOM.getElement(arguments[i]).value == '') {
- return false;
- }
- }
- return true;
- },
- /** @id select */
- select: function (element) {
- MochiKit.DOM.getElement(element).select();
- },
- /** @id activate */
- activate: function (element) {
- element = MochiKit.DOM.getElement(element);
- element.focus();
- if (element.select) {
- element.select();
- }
- },
- /** @id scrollFreeActivate */
- scrollFreeActivate: function (field) {
- setTimeout(function () {
- Field.activate(field);
- }, 1);
- }
- };
- /** @id Autocompleter */
- var Autocompleter = {};
- /** @id Autocompleter.Base */
- Autocompleter.Base = function () {};
- Autocompleter.Base.prototype = {
- /** @id Autocompleter.Base.prototype.baseInitialize */
- baseInitialize: function (element, update, options) {
- this.element = MochiKit.DOM.getElement(element);
- this.update = MochiKit.DOM.getElement(update);
- this.hasFocus = false;
- this.changed = false;
- this.active = false;
- this.index = 0;
- this.entryCount = 0;
- if (this.setOptions) {
- this.setOptions(options);
- }
- else {
- this.options = options || {};
- }
- this.options.paramName = this.options.paramName || this.element.name;
- this.options.tokens = this.options.tokens || [];
- this.options.frequency = this.options.frequency || 0.4;
- this.options.minChars = this.options.minChars || 1;
- this.options.onShow = this.options.onShow || function (element, update) {
- if (!update.style.position || update.style.position == 'absolute') {
- update.style.position = 'absolute';
- MochiKit.Position.clone(element, update, {
- setHeight: false,
- offsetTop: element.offsetHeight
- });
- }
- MochiKit.Visual.appear(update, {duration:0.15});
- };
- this.options.onHide = this.options.onHide || function (element, update) {
- MochiKit.Visual.fade(update, {duration: 0.15});
- };
- if (typeof(this.options.tokens) == 'string') {
- this.options.tokens = new Array(this.options.tokens);
- }
- this.observer = null;
- this.element.setAttribute('autocomplete', 'off');
- MochiKit.Style.hideElement(this.update);
- MochiKit.Signal.connect(this.element, 'onblur', this, this.onBlur);
- MochiKit.Signal.connect(this.element, 'onkeypress', this, this.onKeyPress, this);
- },
- /** @id Autocompleter.Base.prototype.show */
- show: function () {
- if (MochiKit.Style.getStyle(this.update, 'display') == 'none') {
- this.options.onShow(this.element, this.update);
- }
- if (!this.iefix && /MSIE/.test(navigator.userAgent &&
- (MochiKit.Style.getStyle(this.update, 'position') == 'absolute')) {
- new Insertion.After(this.update,
- '<iframe id="' + this.update.id + '_iefix" '+
- 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
- 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
- this.iefix = MochiKit.DOM.getElement(this.update.id + '_iefix');
- }
- if (this.iefix) {
- setTimeout(MochiKit.Base.bind(this.fixIEOverlapping, this), 50);
- }
- },
- /** @id Autocompleter.Base.prototype.fixIEOverlapping */
- fixIEOverlapping: function () {
- MochiKit.Position.clone(this.update, this.iefix);
- this.iefix.style.zIndex = 1;
- this.update.style.zIndex = 2;
- MochiKit.Style.showElement(this.iefix);
- },
- /** @id Autocompleter.Base.prototype.hide */
- hide: function () {
- this.stopIndicator();
- if (MochiKit.Style.getStyle(this.update, 'display') != 'none') {
- this.options.onHide(this.element, this.update);
- }
- if (this.iefix) {
- MochiKit.Style.hideElement(this.iefix);
- }
- },
- /** @id Autocompleter.Base.prototype.startIndicator */
- startIndicator: function () {
- if (this.options.indicator) {
- MochiKit.Style.showElement(this.options.indicator);
- }
- },
- /** @id Autocompleter.Base.prototype.stopIndicator */
- stopIndicator: function () {
- if (this.options.indicator) {
- MochiKit.Style.hideElement(this.options.indicator);
- }
- },
- /** @id Autocompleter.Base.prototype.onKeyPress */
- onKeyPress: function (event) {
- if (this.active) {
- if (event.key().string == "KEY_TAB" || event.key().string == "KEY_RETURN") {
- this.selectEntry();
- MochiKit.Event.stop(event);
- } else if (event.key().string == "KEY_ESCAPE") {
- this.hide();
- this.active = false;
- MochiKit.Event.stop(event);
- return;
- } else if (event.key().string == "KEY_LEFT" || event.key().string == "KEY_RIGHT") {
- return;
- } else if (event.key().string == "KEY_UP") {
- this.markPrevious();
- this.render();
- if (/AppleWebKit'/.test(navigator.appVersion)) {
- event.stop();
- }
- return;
- } else if (event.key().string == "KEY_DOWN") {
- this.markNext();
- this.render();
- if (/AppleWebKit'/.test(navigator.appVersion)) {
- event.stop();
- }
- return;
- }
- } else {
- if (event.key().string == "KEY_TAB" || event.key().string == "KEY_RETURN") {
- return;
- }
- }
- this.changed = true;
- this.hasFocus = true;
- if (this.observer) {
- clearTimeout(this.observer);
- }
- this.observer = setTimeout(MochiKit.Base.bind(this.onObserverEvent, this),
- this.options.frequency*1000);
- },
- /** @id Autocompleter.Base.prototype.findElement */
- findElement: function (event, tagName) {
- var element = event.target;
- while (element.parentNode && (!element.tagName ||
- (element.tagName.toUpperCase() != tagName.toUpperCase()))) {
- element = element.parentNode;
- }
- return element;
- },
- /** @id Autocompleter.Base.prototype.hover */
- onHover: function (event) {
- var element = this.findElement(event, 'LI');
- if (this.index != element.autocompleteIndex) {
- this.index = element.autocompleteIndex;
- this.render();
- }
- event.stop();
- },
- /** @id Autocompleter.Base.prototype.onClick */
- onClick: function (event) {
- var element = this.findElement(event, 'LI');
- this.index = element.autocompleteIndex;
- this.selectEntry();
- this.hide();
- },
- /** @id Autocompleter.Base.prototype.onBlur */
- onBlur: function (event) {
- // needed to make click events working
- setTimeout(MochiKit.Base.bind(this.hide, this), 250);
- this.hasFocus = false;
- this.active = false;
- },
- /** @id Autocompleter.Base.prototype.render */
- render: function () {
- if (this.entryCount > 0) {
- for (var i = 0; i < this.entryCount; i++) {
- this.index == i ?
- MochiKit.DOM.addElementClass(this.getEntry(i), 'selected') :
- MochiKit.DOM.removeElementClass(this.getEntry(i), 'selected');
- }
- if (this.hasFocus) {
- this.show();
- this.active = true;
- }
- } else {
- this.active = false;
- this.hide();
- }
- },
- /** @id Autocompleter.Base.prototype.markPrevious */
- markPrevious: function () {
- if (this.index > 0) {
- this.index--
- } else {
- this.index = this.entryCount-1;
- }
- },
- /** @id Autocompleter.Base.prototype.markNext */
- markNext: function () {
- if (this.index < this.entryCount-1) {
- this.index++
- } else {
- this.index = 0;
- }
- },
- /** @id Autocompleter.Base.prototype.getEntry */
- getEntry: function (index) {
- return this.update.firstChild.childNodes[index];
- },
- /** @id Autocompleter.Base.prototype.getCurrentEntry */
- getCurrentEntry: function () {
- return this.getEntry(this.index);
- },
- /** @id Autocompleter.Base.prototype.selectEntry */
- selectEntry: function () {
- this.active = false;
- this.updateElement(this.getCurrentEntry());
- },
- /** @id Autocompleter.Base.prototype.collectTextNodesIgnoreClass */
- collectTextNodesIgnoreClass: function (element, className) {
- return MochiKit.Base.flattenArray(MochiKit.Base.map(function (node) {
- if (node.nodeType == 3) {
- return node.nodeValue;
- } else if (node.hasChildNodes() && !MochiKit.DOM.hasElementClass(node, className)) {
- return this.collectTextNodesIgnoreClass(node, className);
- }
- return '';
- }, MochiKit.DOM.getElement(element).childNodes)).join('');
- },
- /** @id Autocompleter.Base.prototype.updateElement */
- updateElement: function (selectedElement) {
- if (this.options.updateElement) {
- this.options.updateElement(selectedElement);
- return;
- }
- var value = '';
- if (this.options.select) {
- var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
- if (nodes.length > 0) {
- value = MochiKit.DOM.scrapeText(nodes[0]);
- }
- } else {
- value = this.collectTextNodesIgnoreClass(selectedElement, 'informal');
- }
- var lastTokenPos = this.findLastToken();
- if (lastTokenPos != -1) {
- var newValue = this.element.value.substr(0, lastTokenPos + 1);
- var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
- if (whitespace) {
- newValue += whitespace[0];
- }
- this.element.value = newValue + value;
- } else {
- this.element.value = value;
- }
- this.element.focus();
- if (this.options.afterUpdateElement) {
- this.options.afterUpdateElement(this.element, selectedElement);
- }
- },
- /** @id Autocompleter.Base.prototype.updateChoices */
- updateChoices: function (choices) {
- if (!this.changed && this.hasFocus) {
- this.update.innerHTML = choices;
- var d = MochiKit.DOM;
- d.removeEmptyTextNodes(this.update);
- d.removeEmptyTextNodes(this.update.firstChild);
- if (this.update.firstChild && this.update.firstChild.childNodes) {
- this.entryCount = this.update.firstChild.childNodes.length;
- for (var i = 0; i < this.entryCount; i++) {
- var entry = this.getEntry(i);
- entry.autocompleteIndex = i;
- this.addObservers(entry);
- }
- } else {
- this.entryCount = 0;
- }
- this.stopIndicator();
- this.index = 0;
- this.render();
- }
- },
- /** @id Autocompleter.Base.prototype.addObservers */
- addObservers: function (element) {
- MochiKit.Signal.connect(element, 'onmouseover', this, this.onHover);
- MochiKit.Signal.connect(element, 'onclick', this, this.onClick);
- },
- /** @id Autocompleter.Base.prototype.onObserverEvent */
- onObserverEvent: function () {
- this.changed = false;
- if (this.getToken().length >= this.options.minChars) {
- this.startIndicator();
- this.getUpdatedChoices();
- } else {
- this.active = false;
- this.hide();
- }
- },
- /** @id Autocompleter.Base.prototype.getToken */
- getToken: function () {
- var tokenPos = this.findLastToken();
- if (tokenPos != -1) {
- var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
- } else {
- var ret = this.element.value;
- }
- return /\n/.test(ret) ? '' : ret;
- },
- /** @id Autocompleter.Base.prototype.findLastToken */
- findLastToken: function () {
- var lastTokenPos = -1;
- for (var i = 0; i < this.options.tokens.length; i++) {
- var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
- if (thisTokenPos > lastTokenPos) {
- lastTokenPos = thisTokenPos;
- }
- }
- return lastTokenPos;
- }
- }
- /** @id Ajax.Autocompleter */
- Ajax.Autocompleter = function (element, update, url, options) {
- this.__init__(element, update, url, options);
- };
- MochiKit.Base.update(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype);
- MochiKit.Base.update(Ajax.Autocompleter.prototype, {
- __init__: function (element, update, url, options) {
- this.baseInitialize(element, update, options);
- this.options.asynchronous = true;
- this.options.onComplete = MochiKit.Base.bind(this.onComplete, this);
- this.options.defaultParams = this.options.parameters || null;
- this.url = url;
- },
- /** @id Ajax.Autocompleter.prototype.getUpdatedChoices */
- getUpdatedChoices: function () {
- var entry = encodeURIComponent(this.options.paramName) + '=' +
- encodeURIComponent(this.getToken());
- this.options.parameters = this.options.callback ?
- this.options.callback(this.element, entry) : entry;
- if (this.options.defaultParams) {
- this.options.parameters += '&' + this.options.defaultParams;
- }
- new Ajax.Request(this.url, this.options);
- },
- /** @id Ajax.Autocompleter.prototype.onComplete */
- onComplete: function (request) {
- this.updateChoices(request.responseText);
- }
- });
- /***
- 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.
- ***/
- /** @id Autocompleter.Local */
- Autocompleter.Local = function (element, update, array, options) {
- this.__init__(element, update, array, options);
- };
- MochiKit.Base.update(Autocompleter.Local.prototype, Autocompleter.Base.prototype);
- MochiKit.Base.update(Autocompleter.Local.prototype, {
- __init__: function (element, update, array, options) {
- this.baseInitialize(element, update, options);
- this.options.array = array;
- },
- /** @id Autocompleter.Local.prototype.getUpdatedChoices */
- getUpdatedChoices: function () {
- this.updateChoices(this.options.selector(this));
- },
- /** @id Autocompleter.Local.prototype.setOptions */
- setOptions: function (options) {
- this.options = MochiKit.Base.update({
- choices: 10,
- partialSearch: true,
- partialChars: 2,
- ignoreCase: true,
- fullSearch: false,
- selector: function (instance) {
- var ret = []; // Beginning matches
- var partial = []; // Inside matches
- var entry = instance.getToken();
- var count = 0;
- for (var i = 0; i < instance.options.array.length &&
- ret.length < instance.options.choices ; i++) {
- var elem = instance.options.array[i];
- var foundPos = instance.options.ignoreCase ?
- elem.toLowerCase().indexOf(entry.toLowerCase()) :
- elem.indexOf(entry);
- while (foundPos != -1) {
- if (foundPos === 0 && elem.length != entry.length) {
- ret.push('<li><strong>' + elem.substr(0, entry.length) + '</strong>' +
- elem.substr(entry.length) + '</li>');
- break;
- } else if (entry.length >= instance.options.partialChars &&
- instance.options.partialSearch && foundPos != -1) {
- if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos - 1, 1))) {
- partial.push('<li>' + elem.substr(0, foundPos) + '<strong>' +
- elem.substr(foundPos, entry.length) + '</strong>' + elem.substr(
- foundPos + entry.length) + '</li>');
- break;
- }
- }
- foundPos = instance.options.ignoreCase ?
- elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
- elem.indexOf(entry, foundPos + 1);
- }
- }
- if (partial.length) {
- ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
- }
- return '<ul>' + ret.join('') + '</ul>';
- }
- }, options || {});
- }
- });
- /***
- AJAX in-place editor
- see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
- 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
- ***/
- /** @id Ajax.InPlaceEditor */
- Ajax.InPlaceEditor = function (element, url, options) {
- this.__init__(element, url, options);
- };
- /** @id Ajax.InPlaceEditor.defaultHighlightColor */
- Ajax.InPlaceEditor.defaultHighlightColor = '#FFFF99';
- Ajax.InPlaceEditor.prototype = {
- __init__: function (element, url, options) {
- this.url = url;
- this.element = MochiKit.DOM.getElement(element);
- this.options = MochiKit.Base.update({
- okButton: true,
- okText: 'ok',
- cancelLink: true,
- cancelText: 'cancel',
- savingText: 'Saving...',
- clickToEditText: 'Click to edit',
- okText: 'ok',
- rows: 1,
- onComplete: function (transport, element) {
- new MochiKit.Visual.Highlight(element, {startcolor: this.options.highlightcolor});
- },
- onFailure: function (transport) {
- alert('Error communicating with the server: ' + MochiKit.Base.stripTags(transport.responseText));
- },
- callback: function (form) {
- return MochiKit.DOM.formContents(form);
- },
- handleLineBreaks: true,
- loadingText: 'Loading...',
- savingClassName: 'inplaceeditor-saving',
- loadingClassName: 'inplaceeditor-loading',
- formClassName: 'inplaceeditor-form',
- highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
- highlightendcolor: '#FFFFFF',
- externalControl: null,
- submitOnBlur: false,
- ajaxOptions: {}
- }, options || {});
- if (!this.options.formId && this.element.id) {
- this.options.formId = this.element.id + '-inplaceeditor';
- if (MochiKit.DOM.getElement(this.options.formId)) {
- // there's already a form with that name, don't specify an id
- this.options.formId = null;
- }
- }
- if (this.options.externalControl) {
- this.options.externalControl = MochiKit.DOM.getElement(this.options.externalControl);
- }
- this.originalBackground = MochiKit.Style.getStyle(this.element, 'background-color');
- if (!this.originalBackground) {
- this.originalBackground = 'transparent';
- }
- this.element.title = this.options.clickToEditText;
- this.onclickListener = MochiKit.Signal.connect(this.element, 'onclick', this, this.enterEditMode);
- this.mouseoverListener = MochiKit.Signal.connect(this.element, 'onmouseover', this, this.enterHover);
- this.mouseoutListener = MochiKit.Signal.connect(this.element, 'onmouseout', this, this.leaveHover);
- if (this.options.externalControl) {
- this.onclickListenerExternal = MochiKit.Signal.connect(this.options.externalControl,
- 'onclick', this, this.enterEditMode);
- this.mouseoverListenerExternal = MochiKit.Signal.connect(this.options.externalControl,
- 'onmouseover', this, this.enterHover);
- this.mouseoutListenerExternal = MochiKit.Signal.connect(this.options.externalControl,
- 'onmouseout', this, this.leaveHover);
- }
- },
- /** @id Ajax.InPlaceEditor.prototype.enterEditMode */
- enterEditMode: function (evt) {
- if (this.saving) {
- return;
- }
- if (this.editing) {
- return;
- }
- this.editing = true;
- this.onEnterEditMode();
- if (this.options.externalControl) {
- MochiKit.Style.hideElement(this.options.externalControl);
- }
- MochiKit.Style.hideElement(this.element);
- this.createForm();
- this.element.parentNode.insertBefore(this.form, this.element);
- Field.scrollFreeActivate(this.editField);
- // stop the event to avoid a page refresh in Safari
- if (evt) {
- evt.stop();
- }
- return false;
- },
- /** @id Ajax.InPlaceEditor.prototype.createForm */
- createForm: function () {
- this.form = document.createElement('form');
- this.form.id = this.options.formId;
- MochiKit.DOM.addElementClass(this.form, this.options.formClassName)
- this.form.onsubmit = MochiKit.Base.bind(this.onSubmit, this);
- this.createEditField();
- if (this.options.textarea) {
- var br = document.createElement('br');
- this.form.appendChild(br);
- }
- if (this.options.okButton) {
- okButton = document.createElement('input');
- okButton.type = 'submit';
- okButton.value = this.options.okText;
- this.form.appendChild(okButton);
- }
- if (this.options.cancelLink) {
- cancelLink = document.createElement('a');
- cancelLink.href = '#';
- cancelLink.appendChild(document.createTextNode(this.options.cancelText));
- cancelLink.onclick = MochiKit.Base.bind(this.onclickCancel, this);
- this.form.appendChild(cancelLink);
- }
- },
- /** @id Ajax.InPlaceEditor.prototype.hasHTMLLineBreaks */
- hasHTMLLineBreaks: function (string) {
- if (!this.options.handleLineBreaks) {
- return false;
- }
- return string.match(/<br/i) || string.match(/<p>/i);
- },
- /** @id Ajax.InPlaceEditor.prototype.convertHTMLLineBreaks */
- convertHTMLLineBreaks: function (string) {
- return string.replace(/<br>/gi, '\n').replace(/<br\/>/gi, '\n').replace(/<\/p>/gi, '\n').replace(/<p>/gi, '');
- },
- /** @id Ajax.InPlaceEditor.prototype.createEditField */
- createEditField: function () {
- var text;
- if (this.options.loadTextURL) {
- text = this.options.loadingText;
- } else {
- text = this.getText();
- }
- var obj = this;
- if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
- this.options.textarea = false;
- var textField = document.createElement('input');
- textField.obj = this;
- textField.type = 'text';
- textField.name = 'value';
- textField.value = text;
- textField.style.backgroundColor = this.options.highlightcolor;
- var size = this.options.size || this.options.cols || 0;
- if (size !== 0) {
- textField.size = size;
- }
- if (this.options.submitOnBlur) {
- textField.onblur = MochiKit.Base.bind(this.onSubmit, this);
- }
- this.editField = textField;
- } else {
- this.options.textarea = true;
- var textArea = document.createElement('textarea');
- textArea.obj = this;
- textArea.name = 'value';
- textArea.value = this.convertHTMLLineBreaks(text);
- textArea.rows = this.options.rows;
- textArea.cols = this.options.cols || 40;
- if (this.options.submitOnBlur) {
- textArea.onblur = MochiKit.Base.bind(this.onSubmit, this);
- }
- this.editField = textArea;
- }
- if (this.options.loadTextURL) {
- this.loadExternalText();
- }
- this.form.appendChild(this.editField);
- },
- /** @id Ajax.InPlaceEditor.prototype.getText */
- getText: function () {
- return this.element.innerHTML;
- },
- /** @id Ajax.InPlaceEditor.prototype.loadExternalText */
- loadExternalText: function () {
- MochiKit.DOM.addElementClass(this.form, this.options.loadingClassName);
- this.editField.disabled = true;
- new Ajax.Request(
- this.options.loadTextURL,
- MochiKit.Base.update({
- asynchronous: true,
- onComplete: MochiKit.Base.bind(this.onLoadedExternalText, this)
- }, this.options.ajaxOptions)
- );
- },
- /** @id Ajax.InPlaceEditor.prototype.onLoadedExternalText */
- onLoadedExternalText: function (transport) {
- MochiKit.DOM.removeElementClass(this.form, this.options.loadingClassName);
- this.editField.disabled = false;
- this.editField.value = MochiKit.Base.stripTags(transport);
- },
- /** @id Ajax.InPlaceEditor.prototype.onclickCancel */
- onclickCancel: function () {
- this.onComplete();
- this.leaveEditMode();
- return false;
- },
- /** @id Ajax.InPlaceEditor.prototype.onFailure */
- onFailure: function (transport) {
- this.options.onFailure(transport);
- if (this.oldInnerHTML) {
- this.element.innerHTML = this.oldInnerHTML;
- this.oldInnerHTML = null;
- }
- return false;
- },
- /** @id Ajax.InPlaceEditor.prototype.onSubmit */
- onSubmit: function () {
- // onLoading resets these so we need to save them away for the Ajax call
- var form = this.form;
- var value = this.editField.value;
- // do this first, sometimes the ajax call returns before we get a
- // chance to switch on Saving which means this will actually switch on
- // Saving *after* we have left edit mode causing Saving to be
- // displayed indefinitely
- this.onLoading();
- new Ajax.Updater(
- {
- success: this.element,
- // dont update on failure (this could be an option)
- failure: null
- },
- this.url,
- MochiKit.Base.update({
- parameters: this.options.callback(form, value),
- onComplete: MochiKit.Base.bind(this.onComplete, this),
- onFailure: MochiKit.Base.bind(this.onFailure, this)
- }, this.options.ajaxOptions)
- );
- // stop the event to avoid a page refresh in Safari
- if (arguments.length > 1) {
- arguments[0].stop();
- }
- return false;
- },
- /** @id Ajax.InPlaceEditor.prototype.onLoading */
- onLoading: function () {
- this.saving = true;
- this.removeForm();
- this.leaveHover();
- this.showSaving();
- },
- /** @id Ajax.InPlaceEditor.prototype.onSaving */
- showSaving: function () {
- this.oldInnerHTML = this.element.innerHTML;
- this.element.innerHTML = this.options.savingText;
- MochiKit.DOM.addElementClass(this.element, this.options.savingClassName);
- this.element.style.backgroundColor = this.originalBackground;
- MochiKit.Style.showElement(this.element);
- },
- /** @id Ajax.InPlaceEditor.prototype.removeForm */
- removeForm: function () {
- if (this.form) {
- if (this.form.parentNode) {
- MochiKit.DOM.removeElement(this.form);
- }
- this.form = null;
- }
- },
- /** @id Ajax.InPlaceEditor.prototype.enterHover */
- enterHover: function () {
- if (this.saving) {
- return;
- }
- this.element.style.backgroundColor = this.options.highlightcolor;
- if (this.effect) {
- this.effect.cancel();
- }
- MochiKit.DOM.addElementClass(this.element, this.options.hoverClassName)
- },
- /** @id Ajax.InPlaceEditor.prototype.leaveHover */
- leaveHover: function () {
- if (this.options.backgroundColor) {
- this.element.style.backgroundColor = this.oldBackground;
- }
- MochiKit.DOM.removeElementClass(this.element, this.options.hoverClassName)
- if (this.saving) {
- return;
- }
- this.effect = new MochiKit.Visual.Highlight(this.element, {
- startcolor: this.options.highlightcolor,
- endcolor: this.options.highlightendcolor,
- restorecolor: this.originalBackground
- });
- },
- /** @id Ajax.InPlaceEditor.prototype.leaveEditMode */
- leaveEditMode: function () {
- MochiKit.DOM.removeElementClass(this.element, this.options.savingClassName);
- this.removeForm();
- this.leaveHover();
- this.element.style.backgroundColor = this.originalBackground;
- MochiKit.Style.showElement(this.element);
- if (this.options.externalControl) {
- MochiKit.Style.showElement(this.options.externalControl);
- }
- this.editing = false;
- this.saving = false;
- this.oldInnerHTML = null;
- this.onLeaveEditMode();
- },
- /** @id Ajax.InPlaceEditor.prototype.onComplete */
- onComplete: function (transport) {
- this.leaveEditMode();
- MochiKit.Base.bind(this.options.onComplete, this)(transport, this.element);
- },
- /** @id Ajax.InPlaceEditor.prototype.onEnterEditMode */
- onEnterEditMode: function () {},
- /** @id Ajax.InPlaceEditor.prototype.onLeaveEditMode */
- onLeaveEditMode: function () {},
- /** @id Ajax.InPlaceEditor.prototype.dispose */
- dispose: function () {
- if (this.oldInnerHTML) {
- this.element.innerHTML = this.oldInnerHTML;
- }
- this.leaveEditMode();
- MochiKit.Signal.disconnect(this.onclickListener);
- MochiKit.Signal.disconnect(this.mouseoverListener);
- MochiKit.Signal.disconnect(this.mouseoutListener);
- if (this.options.externalControl) {
- MochiKit.Signal.disconnect(this.onclickListenerExternal);
- MochiKit.Signal.disconnect(this.mouseoverListenerExternal);
- MochiKit.Signal.disconnect(this.mouseoutListenerExternal);
- }
- }
- };
|