IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

Script.aculo.us 1.5 の Autocompleter を Opera、Netscape に対応させて、日本語入力にも対応させて、ちょっとだけ高速化する方法


このコードを Script.aculo.us を読み込んだ後に読み込む。

//	Script.aculo.us 拡張
Object.extend(Autocompleter.Base.prototype, {

	//	for Opera
	___pressReturn : false,

	//	for Opera & Japanese
	//	もとのコンストラクタを退避させる
	___grandBaseInitialize: Autocompleter.Base.prototype.baseInitialize,

	//	for Opera & Japanese
	baseInitialize: function() {

		//	もとのコンストラクタを呼び出す
		this.___grandBaseInitialize.apply(this, $A(arguments));

		//	for Japanese
		//	日本語入力はブラウザによってはイベントを発生させないことがあるため、
		//	Form.Element.Observer を使って変更があった時点で
		//	イベントを発生させる。
		new Form.Element.Observer(
							this.element, 0.01, this.___onChange.bind(this));

		//	for Opera
		//	Opera では Enter が押されると keypress イベントを
		//	キャンセルしても submit されてしまうため submit イベントを
		//	直接キャンセルする必要がある。
		if(this.element.form) {
			Event.observe(this.element.form, 'submit', 
								this.___onSubmit.bindAsEventListener(this));
		}
	},

	//	for Opera
	___disableReturnSubmit: function() {
		this.___pressReturn = true;
	},

	//	for Opera
	___enableReturnSubmit: function() {
		this.___pressReturn = false;
	},

	//	for Opera
	//	Submit をキャプチャする。
	___onSubmit: function(event) {
		if(this.___pressReturn) {
			Event.stop(event);
			this.___enableReturnSubmit();
		}
	},
	
	//	for Japanese
	//	日本語入力のイベントはブラウザ依存のため、
	//	onKeyPress でアクティブにするかどうかの判定は行わない。
	onKeyPress: function(event) {
		if(this.active) {
			switch(event.keyCode) {
			
				case Event.KEY_RETURN:
					//	for Opera
					//	一瞬だけ Return による Submit を抑止する。
					this.___disableReturnSubmit();
					setTimeout(this.___enableReturnSubmit.bind(this), 1);
				
				case Event.KEY_TAB:
					this.selectEntry();
					Event.stop(event);

				case Event.KEY_ESC:
					this.hide();
					this.active = false;
					Event.stop(event);
					return;

				case Event.KEY_LEFT:
				case Event.KEY_RIGHT:
					return;

				case Event.KEY_UP:
					this.markPrevious();
					this.render();
					if(navigator.appVersion.indexOf('AppleWebKit')>0) {
						Event.stop(event);
					}
					return;

				case Event.KEY_DOWN:
					this.markNext();
					this.render();
					if(navigator.appVersion.indexOf('AppleWebKit')>0) {
						Event.stop(event);
					}
					return;
			}
		}
		else {
			if(	event.keyCode == Event.KEY_DOWN ||
				event.keyCode == Event.KEY_UP) {
				
				this.changed = true;
				this.hasFocus = true;

				if(this.observer) {
					clearTimeout(this.observer);
				}
				this.observer = 
					setTimeout(this.onObserverEvent.bind(this),
												this.options.frequency*1000);
			}
		}
	},

	//	forJapanese
	//	this.element に変更があればメニューを開く。
	___onChange: function() {
		this.changed = true;
		this.hasFocus = true;

		if(this.observer) {
			clearTimeout(this.observer);
		}
		this.observer = 
			setTimeout(this.onObserverEvent.bind(this), 
												this.options.frequency*1000);
	}
});



/*----------------------------------------------------------------------------*/

//	prototype 拡張

var _setInterval = {};

var setIntervalEx = function(process, delay) {
	var entry;

	if(typeof process == 'string') {
		entry = new _setInterval.Entry(function(){eval(process);}, delay);
	}
	else if(typeof process == 'function') {
		entry = new _setInterval.Entry(process, delay);
	}
	else {
		throw Error('第一引数が不正です。');
	}

	var id = _setInterval.queue.length;
	_setInterval.queue[id] = entry
	return id;
};

var clearIntervalEx = function(id) {
	_setInterval.queue[id].loop = function(){};
};

_setInterval.queue = [];

_setInterval.Entry = function(process, delay) {
	this.process = process;
	this.delay   = delay;
	this.time    = 0;
};

_setInterval.Entry.prototype.loop = function(time) {
	this.time += time;
	while(this.time >= this.delay) {
		this.process();
		this.time -= this.delay
	}
};

_setInterval.lastTime = new Date().getTime();

setInterval(function() {
	
	var time = new Date().getTime();
	
	var subTime =  time - _setInterval.lastTime;

	_setInterval.lastTime = time;

	for(var i = 0; i < _setInterval.queue.length; i++) {
		_setInterval.queue[i].loop(subTime);
	}

}, 10);

Abstract.TimedObserver.prototype.registerCallback = function() {
	setIntervalEx(this.onTimerEvent.bind(this), this.frequency * 1000);
};

Object.extend(Position, {
	
	___page: function(forElement) {

		var valueT = 0, valueL = 0;

		var element = forElement;
		do {
			valueT += element.offsetTop  || 0;
			valueL += element.offsetLeft || 0;
			// Safari fix
			if (element.offsetParent==document.body) {
				
				if (Element.getStyle(element,'position')=='absolute') {
					break;
				}
			}

		} while (element = element.offsetParent);

		return [valueL, valueT];
	},
	
	clone: function(source, target) {
		var options = Object.extend({
			setLeft:    true,
			setTop:     true,
			setWidth:   true,
			setHeight:  true,
			offsetTop:  0,
			offsetLeft: 0
		}, arguments[2] || {})

		// find page position of source
		source = $(source);
		var p = Position.___page(source);

		// find coordinate system to use
		target = $(target);
		var delta = [0, 0];
		var parent = null;
		// delta [0,0] will do fine with position: fixed elements,
		// position:absolute needs offsetParent deltas
		if (Element.getStyle(target,'position') == 'absolute') {
			parent = Position.offsetParent(target);
			delta = Position.___page(parent);
		}

		// correct by body offsets (fixes Safari)
		if (parent == document.body) {
			delta[0] -= document.body.offsetLeft;
			delta[1] -= document.body.offsetTop;
		}

		// set position
		if(options.setLeft) {
			target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
		}
		if(options.setTop) {
			target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
		}
		if(options.setWidth) {
			target.style.width = source.offsetWidth + 'px';
		}
		if(options.setHeight) {
			target.style.height = source.offsetHeight + 'px';
		}
	}
});

Autocompleter は拡張された二つの種類がある。JavaScript のような言語だと、スーパークラスに直接コードを注入できるので便利だなぁと実感。
コードを使う場合は必ずコメントください。