1/*
2*  $Id: documentselection.js 164 2006-12-30 02:09:25Z wingedfox $
3*  $HeadURL: https://svn.debugger.ru/repos/jslibs/BrowserExtensions/tags/BrowserExtensions.003/documentselection.js $
4*
5*  Class implements cross-browser work with text selection
6*
7*  @author Ilya Lebedev
8*  @author $Author: wingedfox $
9*  @modified $Date: 2006-12-30 05:09:25 +0300 (Сбт, 30 Дек 2006) $
10*  @version $Rev: 164 $
11*  @license LGPL
12*/
13
14/*
15*  @class DocumentSelection
16*/
17DocumentSelection = new function () {
18  var self = this;
19  /*
20  *  Stores hash of keys, applied to elements
21  *
22  *  @type Object
23  *  @scope private
24  */
25  var keys = {
26    'selectionStart' : '__DSselectionStart',
27    'selectionEnd' : '__DSselectionEnd'
28  }
29  /*
30  *  Properties to save current scrolling position in Mozilla
31  *
32  *
33  */
34  var scrollTop, scrollLeft;
35  //---------------------------------------------------------------------------
36  //  SETTERS
37  //---------------------------------------------------------------------------
38  /**
39   *  getSelectionRange wrapper/emulator
40   *  adapted version
41   *
42   *  @see http://www.bazon.net/mishoo/articles.epl?art_id=1292
43   *  @param {HTMLElement}
44   *  @param {Number} start position
45   *  @param {Number} end position
46   *  @param {Boolean} related indicates calculation of range relatively to current start point
47   *  @return void
48   *  @scope public
49   */
50  this.setRange = function(el, start, end, related) {
51    /*
52    *  set range on relative coordinates
53    */
54    if (related) {
55      var st = self.getStart(el);
56      end = st+end;
57      start = st+start;
58    }
59    if ('function' == typeof el.setSelectionRange) {
60      /*
61      *  for Mozilla
62      */
63      try {el.setSelectionRange(start, end)} catch (e) {}
64    } else {
65      /*
66      *  for IE
67      */
68      var range;
69      /*
70      *  just try to create a range....
71      */
72      try {
73        range = el.createTextRange();
74      } catch(e) {
75        try {
76          range = document.body.createTextRange();
77          range.moveToElementText(el);
78        } catch(e) {
79          range = false;
80        }
81      }
82      // if cannot create range
83      if (!range) return false;
84      range.collapse(true);
85
86      range.moveStart("character", start);
87      range.moveEnd("character", end - start);
88      range.select();
89    }
90    self.setCursorPosition(el,start,end);
91  }
92  /**
93   *  Set sursor position for supplied child
94   *
95   *  @param {HTMLElement} element to set cursor position on
96   *  @param {Number} start selection start
97   *  @param {Number} end selection end
98   *  @scope public
99   */
100  this.setCursorPosition = function (el,start,end) {
101    el[keys['selectionStart']] = parseInt(start);
102    el[keys['selectionEnd']] = parseInt(end);
103//    if (scrollTop) el.scrollTop  = scrollTop;
104//    if (scrollLeft) el.scrollLeft = scrollLeft;
105//    scrollTop = null;
106//    scrollLeft= null;
107  }
108  //---------------------------------------------------------------------------
109  //  GETTERS
110  //---------------------------------------------------------------------------
111  /**
112   *  Return contents of the current selection
113   *
114   *  @param {HTMLElement} el element to look position on
115   *  @return {String}
116   *  @scope public
117   */
118  this.getSelection = function(el) {
119    var s = self.getCursorPosition(el),
120        e = self.getEnd(el);
121    /*
122    *  w/o this check content might be duplicated on delete
123    */
124    if (e<s) e = s;
125    /*
126    *  check for IE, because Opera does use \r\n sequence, but calculate positions correctly
127    */
128    var tmp = document.selection?el.value.replace(/\r/g,""):el.value;
129    return tmp.substring(s,e);
130  }
131  /**
132   *  getSelectionStart wrapper/emulator
133   *  adapted version
134   *
135   *  @see http://www.bazon.net/mishoo/articles.epl?art_id=1292
136  *  @param {HTMLElement} el element to calculate end position for
137  *  @param {HTMLElement} force force calculation
138   *  @return {Number} start position
139   *  @scope public
140   */
141  this.getStart = function (el, force) {
142    var start;
143    /*
144    *  for IE
145    */
146    try {
147      start = Math.abs(document.selection.createRange().moveStart("character", -100000000)); // start
148      if (start>0 || force) {
149        try {
150          var endReal = Math.abs(el.createTextRange().moveEnd("character", -100000000));
151          /*
152          *  calculate node offset
153          */
154          var r = document.body.createTextRange();
155          r.moveToElementText(el);
156          var sTest = Math.abs(r.moveStart("character", -100000000));
157          var eTest = Math.abs(r.moveEnd("character", -100000000));
158          /*
159          *  test for the TEXTAREA's dumb behavior
160          */
161          if (el.tagName.toLowerCase() != 'input' && eTest - endReal == sTest) {
162            start -= sTest;
163          }
164        } catch(err) {}
165      }
166    } catch (e) {
167      /*
168      *  for Mozilla
169      */
170      try { start = el.selectionStart } catch (e) { start = -1 }
171    }
172    return start<1?(start==0&&force?0:(parseInt(el[keys['selectionStart']])?parseInt(el[keys['selectionStart']]):0)):start;
173  }
174  /*
175  *  getSelectionStart wrapper/emulator
176  *  adapted version
177  *
178  *  @see http://www.bazon.net/mishoo/articles.epl?art_id=1292
179  *  @param {HTMLElement} el element to calculate end position for
180  *  @param {HTMLElement} force force calculation
181  *  @return {Number} start position
182  *  @scope public
183  */
184  this.getEnd = function (el,force) {
185    var end;
186    /*
187    *  for IE
188    */
189    try {
190      end = Math.abs(document.selection.createRange().moveEnd("character", -100000000)); // end
191      if (end>0 || force) {
192        try {
193          var endReal = Math.abs(el.createTextRange().moveEnd("character", -100000000));
194          /*
195          *  calculate node offset
196          */
197          var r = document.body.createTextRange();
198          r.moveToElementText(el);
199          var sTest = Math.abs(r.moveStart("character", -100000000));
200          var eTest = Math.abs(r.moveEnd("character", -100000000));
201          /*
202          *  test for the TEXTAREA's dumb behavior
203          */
204          if (el.tagName.toLowerCase() != 'input' && eTest - endReal == sTest) {
205            end -= sTest;
206          }
207        } catch(err) {}
208      }
209    } catch (e) {
210      /*
211      *  for Mozilla
212      */
213      try { end = el.selectionEnd } catch (e) { end = -1 }
214    }
215    return end<1?(end==0&&force?0:(parseInt(el[keys['selectionEnd']])?parseInt(el[keys['selectionEnd']]):0)):end;
216  }
217  /*
218  *  Return cursor position for supplied field
219  *
220  *  @param {HTMLElement} element to get cursor position from
221  *  @return {Number} position
222  *  @scope public
223  */
224  this.getCursorPosition = function (el) {
225//    scrollTop  = el.scrollTop;
226//    scrollLeft = el.scrollLeft;
227
228    return self.getStart(el);
229  }
230  //---------------------------------------------------------------------------
231  //  MICS FUNCTIONS
232  //---------------------------------------------------------------------------
233  /*
234  *  Used to save cursor position on click.
235  *
236  *  @param {MouseEvent} click event
237  *  @scope protected
238  */
239  this.saveCursorPosition = function (e) {
240    var el = e.srcElement||e.target;
241    if (!el || el.tagName.toLowerCase() == 'select') return false;
242    self.setCursorPosition(el,self.getStart(el,true),self.getEnd(el,true));
243  }
244  /*
245  *  Insert text at cursor position
246  *
247  *  @param {HTMLElement} text field to insert text
248  *  @param {String} text to insert
249  *  @scope public
250  */
251  this.insertAtCursor = function (fld, val) {
252    var r = self.getCursorPosition(fld);
253    /*
254    *  check for IE, because Opera does use \r\n sequence, but calculate positions correctly
255    */
256    var tmp = document.selection?fld.value.replace(/\r/g,""):fld.value;
257    fld.value = tmp.substring(0, r)+val+tmp.substring(r,tmp.length);
258    self.setRange(fld,r+val.length,r+val.length);
259  }
260  /*
261  *  Deletes char at cursor position
262  *
263  *  @param {HTMLElement} text field to delete text
264  *  @param {Boolean} delete text before (backspace) or after (del) cursor
265  *  @scope public
266  */
267  this.deleteAtCursor = function (fld, after) {
268    if (!after) after = false;
269    var r = self.getCursorPosition(fld),
270        e = self.getEnd(fld);
271    /*
272    *  w/o this check content might be duplicated on delete
273    */
274    if (e<r) e = r;
275    if (r==e) {
276      r=after?r:r-1<0?0:r-1;
277      e=after?e+1:e;
278    }
279    /*
280    *  check for IE, because Opera does use \r\n sequence, but calculate positions correctly
281    */
282    var tmp = document.selection?fld.value.replace(/\r/g,""):fld.value;
283    fld.value = tmp.substring(0, r)+tmp.substring(e,tmp.length);
284    self.setRange(fld, r, r);
285  }
286  /**
287   *  Removes the selection, if available
288   *
289   *  @param {HTMLElement} fld field to delete text from
290   *  @scope public
291   */
292  this.deleteSelection = function (fld) {
293    var r = self.getCursorPosition(fld),
294        e = self.getEnd(fld);
295    if (r==e) return;
296    /*
297    *  check for IE, because Opera does use \r\n sequence, but calculate positions correctly
298    */
299    var tmp = document.selection?fld.value.replace(/\r/g,""):fld.value;
300    fld.value = tmp.substring(0, r)+tmp.substring(e,tmp.length);
301    self.setRange(fld, r, r);
302  }
303}
304/*
305*  Add cursor position saving
306*/
307if (document.attachEvent) {
308  document.attachEvent('onmouseup', DocumentSelection.saveCursorPosition);
309  document.attachEvent('onkeyup', DocumentSelection.saveCursorPosition);
310} else if (document.addEventListener) {
311  document.addEventListener('mouseup', DocumentSelection.saveCursorPosition,false);
312  document.addEventListener('keyup', DocumentSelection.saveCursorPosition,false);
313}
314