//------------------------------------------------------------
// 13.04.2008 (C) Walter Bislin; http://walter.bislins.ch/doku/xtc
// 02.10.2009 added SetScrollTop, GetScrollTop
// 07.05.2014 fixed: GetCursorScrollTop() works now in all browsers correct
// 07.05.2014 added: GetLineHeight()
//
// xTextControl class that encapsulates text controls for browser independence:
// To ensure browser independence do not use properties of text controls directly!
// NL sould be a single '\n' character, not '\r\n', so positions of carets
// correspond to character positions in strings on all browsers.
//
// usage:
//
// var textControl = new xTextControl(document.myForm.myTextInput);
// alert( textControl.Text );
// alert( textControl.SelText ); etc.
//
// Note: all members are ment to be read only!
// Use functions to change the control!
// You must not directly change the control properties!
//
// Note:
// range.moveToElementText(ele) on IE throws an exception if ele is a text input field!?
// That means, that xTextControl only works with textarea on all modern browsers :-(
function xNormString( aStr ) {
return aStr.replace(/\r\n/g,'\n');
}
function xTextControl( aTextControl ) {
// aTextControl must be a textarea element
this.TextControl = aTextControl;
this.SelStart = 0;
this.SelEnd = 0;
this.SelText = '';
this.Text = '';
this.Enabled = this.GetDataFromControl();
this.LineHeight = 0;
var ft = '';
for (var i = 0; i < 32; i++) { ft += 'x\nx\nx\nx\nx\nx\nx\nx\n'; }
this.FillText = ft;
}
xTextControl.prototype.GetDataFromControl = function() {
if (typeof(this.TextControl) == 'undefined') {return false;}
this.Text = xNormString(this.TextControl.value);
if (document.selection) { // IE
this.TextControl.focus();
// find start of originalRange with a binary search
var originalRange = document.selection.createRange();
var testRange = originalRange.duplicate();
testRange.moveToElementText( this.TextControl );
var len = this.Text.length;
var pos = 0;
var step = Math.floor( len / 2 ) + 1;
while (step > 0) {
while ((pos+step <= len) && testRange.inRange(originalRange)) {
testRange.moveStart( 'character', step );
pos += step;
}
if (!testRange.inRange(originalRange)) {
testRange.moveStart( 'character', -step );
pos -= step;
}
step = Math.floor( step / 2 );
}
this.SelStart = pos;
// find end of originalRange with a binary search
testRange.moveToElementText( this.TextControl );
var pos = len;
var step = Math.floor( len / 2 ) + 1;
while (step > 0) {
while ((pos-step >= 0) && testRange.inRange(originalRange)) {
testRange.moveEnd( 'character', -step );
pos -= step;
}
if (!testRange.inRange(originalRange)) {
testRange.moveEnd( 'character', step );
pos += step;
}
step = Math.floor( step / 2 );
}
this.SelEnd = pos;
this.SelText = this.Text.substring( this.SelStart, this.SelEnd );
return true;
}
else if (this.TextControl.setSelectionRange) { // Mozilla
this.SelStart = this.TextControl.selectionStart;
this.SelEnd = this.TextControl.selectionEnd;
this.SelText = this.Text.substring( this.SelStart, this.SelEnd );
return true;
}
return false;
};
xTextControl.prototype.GetScrollTop = function() {
if (typeof(this.TextControl.scrollTop) == 'number') {
return this.TextControl.scrollTop;
}
return 0;
}
xTextControl.prototype.SetScrollTop = function( aTop ) {
if (typeof(this.TextControl.scrollTop) == 'number') {
this.TextControl.scrollTop = aTop;
}
}
xTextControl.prototype.GetLineHeight = function() {
if (this.LineHeight == 0) {
var dummy = this.GetCursorScrollTop();
}
return this.LineHeight;
}
xTextControl.prototype.GetCursorScrollTop = function() {
// Implementation: Cut the text after cursor pos and read scrollHight.
// This is the scrollTop of the cursor position. After that restore the TextControl0
if (typeof(this.TextControl.scrollHeight) != 'number') { return 0; }
// save current text and selection
var startPos = this.SelStart;
var endPos = this.SelEnd;
var fullText = this.Text;
// Implementation: Some browsers return window height instead of document height in scrollHeight.
// To get the effective scroll height, I fill the docu with FillText, get the scrollHeight as an offset,
// add the topText, get and the scroll height again. The difference of the scroll heights is the real scroll height.
this.SetText( this.FillText );
var topOffset = this.TextControl.scrollHeight;
// cut text after cursor position
var topText = this.FillText + fullText.substring( 0, startPos );
this.SetText( topText );
// read scrollHeight = scrollTop of cursor
var top = this.TextControl.scrollHeight - topOffset;
// get height of a single line
this.SetText( topText + '\nx' );
var lineHeight = this.TextControl.scrollHeight - top - topOffset;
this.LineHeight = lineHeight;
// restore TextControl
this.SetText( fullText );
this.SetSelectionRange( startPos, endPos, true );
return top;
}
xTextControl.prototype.SetText = function( aText ) {
// Requires normalized aText
// Note: this invalidates the selection members.
// Use GetDataFromControl to get updated or
// use SetSelectionRange to set a new selection
if (this.TextControl.setSelectionRange) { // Mozilla
var scrollTop = this.TextControl.scrollTop;
this.TextControl.value = aText;
this.TextControl.scrollTop = scrollTop;
} else {
this.TextControl.value = aText;
}
this.Text = aText;
};
xTextControl.prototype.ChangeSelection = function( aSelStart, aSelEnd, aReplaceText, aUpdateControlSelection ) {
// Note: the controls selection is only updated if
// aUpdateControlSelection is true. You may update the controls selection via UpdateControlSelection() manually.
var preText = this.Text.substring( 0, aSelStart );
var pstText = this.Text.substring( aSelEnd );
var newText = preText + aReplaceText + pstText;
this.SetText( newText );
this.SelStart = aSelStart;
this.SelEnd = aSelStart + aReplaceText.length;
this.SelText = aReplaceText;
if (aUpdateControlSelection) {this.UpdateControlSelection();}
};
xTextControl.prototype.SetSelectionRange = function( aSelStart, aSelEnd, aUpdateControlSelection ) {
// Note: the controls selection is only updated if
// aUpdateControlSelection is true. You may update the controls selection via UpdateControlSelection() manually.
this.SelStart = aSelStart;
this.SelEnd = aSelEnd;
this.SelText = this.Text.substring( aSelStart, aSelEnd );
if (aUpdateControlSelection) {this.UpdateControlSelection();}
};
xTextControl.prototype.ChangeSelectionText = function( aReplaceText, aUpdateControlSelection ) {
// Note: the controls selection is only updated if
// aUpdateControlSelection is true. You may update the controls selection via UpdateControlSelection() manually.
this.ChangeSelection( this.SelStart, this.SelEnd, aReplaceText, aUpdateControlSelection );
};
xTextControl.prototype.SetCaretPos = function( aPos ) {
this.SetSelectionRange( aPos, aPos, true );
};
xTextControl.prototype.UpdateControlSelection = function() {
// updates the text control selection to the current values.
this.TextControl.focus();
if (this.TextControl.setSelectionRange) { // MOZILLA
this.TextControl.setSelectionRange( this.SelStart, this.SelEnd );
}
else if (this.TextControl.createTextRange) { // IE
var range = this.TextControl.createTextRange();
range.collapse( true );
range.moveEnd( 'character', this.SelEnd );
range.moveStart( 'character', this.SelStart );
range.select();
}
};