SlideShare a Scribd company logo
1 of 7
// Cross-broswer implementation of text ranges and selections
// documentation: http://bililite.com/blog/2011/01/17/cross-browser-text-ranges-
and-selections/
// Version: 1.1
// Copyright (c) 2010 Daniel Wachsstock
// MIT license:
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
(function(){
bililiteRange = function(el, debug){
var ret;
if (debug){
ret = new NothingRange(); // Easier to force it to use the no-
selection type than to try to find an old browser
}else if (document.selection){
// Internet Explorer
ret = new IERange();
}else if (window.getSelection && el.setSelectionRange){
// Standards. Element is an input or textarea
ret = new InputRange();
}else if (window.getSelection){
// Standards, with any other kind of element
ret = new W3CRange();
}else{
// doesn't support selection
ret = new NothingRange();
}
ret._el = el;
ret._textProp = textProp(el);
ret._bounds = [0, ret.length()];
return ret;
};
function textProp(el){
// returns the property that contains the text of the element
if (typeof el.value != 'undefined') return 'value';
if (typeof el.text != 'undefined') return 'text';
if (typeof el.textContent != 'undefined') return 'textContent';
return 'innerText';
}
// base class
function Range(){}
Range.prototype = {
length: function() {
return this._el[this._textProp].replace(/r/g, '').length; // need
to correct for IE's CrLf weirdness
},
bounds: function(s){
if (s === 'all'){
this._bounds = [0, this.length()];
}else if (s === 'start'){
this._bounds = [0, 0];
}else if (s === 'end'){
this._bounds = [this.length(), this.length()];
}else if (s === 'selection'){
this.bounds ('all'); // first select the whole thing for
constraining
this._bounds = this._nativeSelection();
}else if (s){
this._bounds = s; // don't error check now; the element may
change at any moment, so constrain it when we need it.
}else{
var b = [
Math.max(0, Math.min (this.length(), this._bounds[0])),
Math.max(0, Math.min (this.length(), this._bounds[1]))
];
return b; // need to constrain it to fit
}
return this; // allow for chaining
},
select: function(){
this._nativeSelect(this._nativeRange(this.bounds()));
return this; // allow for chaining
},
text: function(text, select){
if (arguments.length){
this._nativeSetText(text, this._nativeRange(this.bounds()));
if (select == 'start'){
this.bounds ([this._bounds[0], this._bounds[0]]);
this.select();
}else if (select == 'end'){
this.bounds ([this._bounds[0]+text.length,
this._bounds[0]+text.length]);
this.select();
}else if (select == 'all'){
this.bounds ([this._bounds[0],
this._bounds[0]+text.length]);
this.select();
}
return this; // allow for chaining
}else{
return this._nativeGetText(this._nativeRange(this.bounds()));
}
},
insertEOL: function (){
this._nativeEOL();
this._bounds = [this._bounds[0]+1, this._bounds[0]+1]; // move past
the EOL marker
return this;
}
};
function IERange(){}
IERange.prototype = new Range();
IERange.prototype._nativeRange = function (bounds){
var rng;
if (this._el.tagName == 'INPUT'){
// IE 8 is very inconsistent; textareas have createTextRange but it
doesn't work
rng = this._el.createTextRange();
}else{
rng = document.body.createTextRange ();
rng.moveToElementText(this._el);
}
if (bounds){
if (bounds[1] < 0) bounds[1] = 0; // IE tends to run elements out of
bounds
if (bounds[0] > this.length()) bounds[0] = this.length();
if (bounds[1] < rng.text.replace(/r/g, '').length){ // correct for
IE's CrLf wierdness
// block-display elements have an invisible, uncounted end of
element marker, so we move an extra one and use the current length of the range
rng.moveEnd ('character', -1);
rng.moveEnd ('character', bounds[1]-rng.text.replace(/r/g,
'').length);
}
if (bounds[0] > 0) rng.moveStart('character', bounds[0]);
}
return rng;
};
IERange.prototype._nativeSelect = function (rng){
rng.select();
};
IERange.prototype._nativeSelection = function (){
// returns [start, end] for the selection constrained to be in element
var rng = this._nativeRange(); // range of the element to constrain to
var len = this.length();
if (document.selection.type != 'Text') return [len, len]; // append to the
end
var sel = document.selection.createRange();
try{
return [
iestart(sel, rng),
ieend (sel, rng)
];
}catch (e){
// IE gets upset sometimes about comparing text to input elements,
but the selections cannot overlap, so make a best guess
return (sel.parentElement().sourceIndex < this._el.sourceIndex) ?
[0,0] : [len, len];
}
};
IERange.prototype._nativeGetText = function (rng){
return rng.text.replace(/r/g, ''); // correct for IE's CrLf weirdness
};
IERange.prototype._nativeSetText = function (text, rng){
rng.text = text;
};
IERange.prototype._nativeEOL = function(){
if (typeof this._el.value != 'undefined'){
this.text('n'); // for input and textarea, insert it straight
}else{
this._nativeRange(this.bounds()).pasteHTML('<br/>');
}
};
// IE internals
function iestart(rng, constraint){
// returns the position (in character) of the start of rng within
constraint. If it's not in constraint, returns 0 if it's before, length if it's
after
var len = constraint.text.replace(/r/g, '').length; // correct for IE's
CrLf wierdness
if (rng.compareEndPoints ('StartToStart', constraint) <= 0) return 0; //
at or before the beginning
if (rng.compareEndPoints ('StartToEnd', constraint) >= 0) return len;
for (var i = 0; rng.compareEndPoints ('StartToStart', constraint) > 0; +
+i, rng.moveStart('character', -1));
return i;
}
function ieend (rng, constraint){
// returns the position (in character) of the end of rng within
constraint. If it's not in constraint, returns 0 if it's before, length if it's
after
var len = constraint.text.replace(/r/g, '').length; // correct for IE's
CrLf wierdness
if (rng.compareEndPoints ('EndToEnd', constraint) >= 0) return len; // at
or after the end
if (rng.compareEndPoints ('EndToStart', constraint) <= 0) return 0;
for (var i = 0; rng.compareEndPoints ('EndToStart', constraint) > 0; ++i,
rng.moveEnd('character', -1));
return i;
}
// an input element in a standards document. "Native Range" is just the bounds
array
function InputRange(){}
InputRange.prototype = new Range();
InputRange.prototype._nativeRange = function(bounds) {
return bounds || [0, this.length()];
};
InputRange.prototype._nativeSelect = function (rng){
this._el.setSelectionRange(rng[0], rng[1]);
};
InputRange.prototype._nativeSelection = function(){
return [this._el.selectionStart, this._el.selectionEnd];
};
InputRange.prototype._nativeGetText = function(rng){
return this._el.value.substring(rng[0], rng[1]);
};
InputRange.prototype._nativeSetText = function(text, rng){
var val = this._el.value;
this._el.value = val.substring(0, rng[0]) + text + val.substring(rng[1]);
};
InputRange.prototype._nativeEOL = function(){
this.text('n');
};
function W3CRange(){}
W3CRange.prototype = new Range();
W3CRange.prototype._nativeRange = function (bounds){
var rng = document.createRange();
rng.selectNodeContents(this._el);
if (bounds){
w3cmoveBoundary (rng, bounds[0], true, this._el);
rng.collapse (true);
w3cmoveBoundary (rng, bounds[1]-bounds[0], false, this._el);
}
return rng;
};
W3CRange.prototype._nativeSelect = function (rng){
window.getSelection().removeAllRanges();
window.getSelection().addRange (rng);
};
W3CRange.prototype._nativeSelection = function (){
// returns [start, end] for the selection constrained to be in
element
var rng = this._nativeRange(); // range of the element to constrain
to
if (window.getSelection().rangeCount == 0) return [this.length(),
this.length()]; // append to the end
var sel = window.getSelection().getRangeAt(0);
return [
w3cstart(sel, rng),
w3cend (sel, rng)
];
};
W3CRange.prototype._nativeGetText = function (rng){
return rng.toString();
};
W3CRange.prototype._nativeSetText = function (text, rng){
rng.deleteContents();
rng.insertNode (document.createTextNode(text));
this._el.normalize(); // merge the text with the surrounding text
};
W3CRange.prototype._nativeEOL = function(){
var rng = this._nativeRange(this.bounds());
rng.deleteContents();
var br = document.createElement('br');
br.setAttribute ('_moz_dirty', ''); // for Firefox
rng.insertNode (br);
rng.insertNode (document.createTextNode('n'));
rng.collapse (false);
};
// W3C internals
function nextnode (node, root){
// in-order traversal
// we've already visited node, so get kids then siblings
if (node.firstChild) return node.firstChild;
if (node.nextSibling) return node.nextSibling;
if (node===root) return null;
while (node.parentNode){
// get uncles
node = node.parentNode;
if (node == root) return null;
if (node.nextSibling) return node.nextSibling;
}
return null;
}
function w3cmoveBoundary (rng, n, bStart, el){
// move the boundary (bStart == true ? start : end) n characters forward,
up to the end of element el. Forward only!
// if the start is moved after the end, then an exception is raised
if (n <= 0) return;
var node = rng[bStart ? 'startContainer' : 'endContainer'];
if (node.nodeType == 3){
// we may be starting somewhere into the text
n += rng[bStart ? 'startOffset' : 'endOffset'];
}
while (node){
if (node.nodeType == 3){
if (n <= node.nodeValue.length){
rng[bStart ? 'setStart' : 'setEnd'](node, n);
// special case: if we end next to a <br>, include that
node.
if (n == node.nodeValue.length){
// skip past zero-length text nodes
for (var next = nextnode (node, el); next &&
next.nodeType==3 && next.nodeValue.length == 0; next = nextnode(next, el)){
rng[bStart ? 'setStartAfter' :
'setEndAfter'](next);
}
if (next && next.nodeType == 1 && next.nodeName ==
"BR") rng[bStart ? 'setStartAfter' : 'setEndAfter'](next);
}
return;
}else{
rng[bStart ? 'setStartAfter' : 'setEndAfter'](node); //
skip past this one
n -= node.nodeValue.length; // and eat these characters
}
}
node = nextnode (node, el);
}
}
var START_TO_START = 0; // from the w3c definitions
var START_TO_END = 1;
var END_TO_END = 2;
var END_TO_START = 3;
// from the Mozilla documentation, for range.compareBoundaryPoints(how,
sourceRange)
// -1, 0, or 1, indicating whether the corresponding boundary-point of range is
respectively before, equal to, or after the corresponding boundary-point of
sourceRange.
// * Range.END_TO_END compares the end boundary-point of sourceRange to the
end boundary-point of range.
// * Range.END_TO_START compares the end boundary-point of sourceRange to
the start boundary-point of range.
// * Range.START_TO_END compares the start boundary-point of sourceRange to
the end boundary-point of range.
// * Range.START_TO_START compares the start boundary-point of sourceRange
to the start boundary-point of range.
function w3cstart(rng, constraint){
if (rng.compareBoundaryPoints (START_TO_START, constraint) <= 0) return 0;
// at or before the beginning
if (rng.compareBoundaryPoints (END_TO_START, constraint) >= 0) return
constraint.toString().length;
rng = rng.cloneRange(); // don't change the original
rng.setEnd (constraint.endContainer, constraint.endOffset); // they now
end at the same place
return constraint.toString().length - rng.toString().length;
}
function w3cend (rng, constraint){
if (rng.compareBoundaryPoints (END_TO_END, constraint) >= 0) return
constraint.toString().length; // at or after the end
if (rng.compareBoundaryPoints (START_TO_END, constraint) <= 0) return 0;
rng = rng.cloneRange(); // don't change the original
rng.setStart (constraint.startContainer, constraint.startOffset); // they
now start at the same place
return rng.toString().length;
}
function NothingRange(){}
NothingRange.prototype = new Range();
NothingRange.prototype._nativeRange = function(bounds) {
return bounds || [0,this.length()];
};
NothingRange.prototype._nativeSelect = function (rng){ // do nothing
};
NothingRange.prototype._nativeSelection = function(){
return [0,0];
};
NothingRange.prototype._nativeGetText = function (rng){
return this._el[this._textProp].substring(rng[0], rng[1]);
};
NothingRange.prototype._nativeSetText = function (text, rng){
var val = this._el[this._textProp];
this._el[this._textProp] = val.substring(0, rng[0]) + text +
val.substring(rng[1]);
};
NothingRange.prototype._nativeEOL = function(){
this.text('n');
};
})();

More Related Content

Featured

2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by HubspotMarius Sescu
 
Everything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTEverything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTExpeed Software
 
Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsPixeldarts
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthThinkNow
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfmarketingartwork
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024Neil Kimberley
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)contently
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024Albert Qian
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsKurio // The Social Media Age(ncy)
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Search Engine Journal
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summarySpeakerHub
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next Tessa Mero
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentLily Ray
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best PracticesVit Horky
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project managementMindGenius
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...RachelPearson36
 

Featured (20)

2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot
 
Everything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTEverything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPT
 
Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage Engineerings
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental Health
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
 
Skeleton Culture Code
Skeleton Culture CodeSkeleton Culture Code
Skeleton Culture Code
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie Insights
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search Intent
 
How to have difficult conversations
How to have difficult conversations How to have difficult conversations
How to have difficult conversations
 
Introduction to Data Science
Introduction to Data ScienceIntroduction to Data Science
Introduction to Data Science
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best Practices
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project management
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
 

Bililite range

  • 1. // Cross-broswer implementation of text ranges and selections // documentation: http://bililite.com/blog/2011/01/17/cross-browser-text-ranges- and-selections/ // Version: 1.1 // Copyright (c) 2010 Daniel Wachsstock // MIT license: // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. (function(){ bililiteRange = function(el, debug){ var ret; if (debug){ ret = new NothingRange(); // Easier to force it to use the no- selection type than to try to find an old browser }else if (document.selection){ // Internet Explorer ret = new IERange(); }else if (window.getSelection && el.setSelectionRange){ // Standards. Element is an input or textarea ret = new InputRange(); }else if (window.getSelection){ // Standards, with any other kind of element ret = new W3CRange(); }else{ // doesn't support selection ret = new NothingRange(); } ret._el = el; ret._textProp = textProp(el); ret._bounds = [0, ret.length()]; return ret; }; function textProp(el){ // returns the property that contains the text of the element if (typeof el.value != 'undefined') return 'value'; if (typeof el.text != 'undefined') return 'text'; if (typeof el.textContent != 'undefined') return 'textContent'; return 'innerText'; } // base class function Range(){} Range.prototype = {
  • 2. length: function() { return this._el[this._textProp].replace(/r/g, '').length; // need to correct for IE's CrLf weirdness }, bounds: function(s){ if (s === 'all'){ this._bounds = [0, this.length()]; }else if (s === 'start'){ this._bounds = [0, 0]; }else if (s === 'end'){ this._bounds = [this.length(), this.length()]; }else if (s === 'selection'){ this.bounds ('all'); // first select the whole thing for constraining this._bounds = this._nativeSelection(); }else if (s){ this._bounds = s; // don't error check now; the element may change at any moment, so constrain it when we need it. }else{ var b = [ Math.max(0, Math.min (this.length(), this._bounds[0])), Math.max(0, Math.min (this.length(), this._bounds[1])) ]; return b; // need to constrain it to fit } return this; // allow for chaining }, select: function(){ this._nativeSelect(this._nativeRange(this.bounds())); return this; // allow for chaining }, text: function(text, select){ if (arguments.length){ this._nativeSetText(text, this._nativeRange(this.bounds())); if (select == 'start'){ this.bounds ([this._bounds[0], this._bounds[0]]); this.select(); }else if (select == 'end'){ this.bounds ([this._bounds[0]+text.length, this._bounds[0]+text.length]); this.select(); }else if (select == 'all'){ this.bounds ([this._bounds[0], this._bounds[0]+text.length]); this.select(); } return this; // allow for chaining }else{ return this._nativeGetText(this._nativeRange(this.bounds())); } }, insertEOL: function (){ this._nativeEOL(); this._bounds = [this._bounds[0]+1, this._bounds[0]+1]; // move past the EOL marker return this; } }; function IERange(){} IERange.prototype = new Range(); IERange.prototype._nativeRange = function (bounds){ var rng;
  • 3. if (this._el.tagName == 'INPUT'){ // IE 8 is very inconsistent; textareas have createTextRange but it doesn't work rng = this._el.createTextRange(); }else{ rng = document.body.createTextRange (); rng.moveToElementText(this._el); } if (bounds){ if (bounds[1] < 0) bounds[1] = 0; // IE tends to run elements out of bounds if (bounds[0] > this.length()) bounds[0] = this.length(); if (bounds[1] < rng.text.replace(/r/g, '').length){ // correct for IE's CrLf wierdness // block-display elements have an invisible, uncounted end of element marker, so we move an extra one and use the current length of the range rng.moveEnd ('character', -1); rng.moveEnd ('character', bounds[1]-rng.text.replace(/r/g, '').length); } if (bounds[0] > 0) rng.moveStart('character', bounds[0]); } return rng; }; IERange.prototype._nativeSelect = function (rng){ rng.select(); }; IERange.prototype._nativeSelection = function (){ // returns [start, end] for the selection constrained to be in element var rng = this._nativeRange(); // range of the element to constrain to var len = this.length(); if (document.selection.type != 'Text') return [len, len]; // append to the end var sel = document.selection.createRange(); try{ return [ iestart(sel, rng), ieend (sel, rng) ]; }catch (e){ // IE gets upset sometimes about comparing text to input elements, but the selections cannot overlap, so make a best guess return (sel.parentElement().sourceIndex < this._el.sourceIndex) ? [0,0] : [len, len]; } }; IERange.prototype._nativeGetText = function (rng){ return rng.text.replace(/r/g, ''); // correct for IE's CrLf weirdness }; IERange.prototype._nativeSetText = function (text, rng){ rng.text = text; }; IERange.prototype._nativeEOL = function(){ if (typeof this._el.value != 'undefined'){ this.text('n'); // for input and textarea, insert it straight }else{ this._nativeRange(this.bounds()).pasteHTML('<br/>'); } }; // IE internals function iestart(rng, constraint){ // returns the position (in character) of the start of rng within constraint. If it's not in constraint, returns 0 if it's before, length if it's after
  • 4. var len = constraint.text.replace(/r/g, '').length; // correct for IE's CrLf wierdness if (rng.compareEndPoints ('StartToStart', constraint) <= 0) return 0; // at or before the beginning if (rng.compareEndPoints ('StartToEnd', constraint) >= 0) return len; for (var i = 0; rng.compareEndPoints ('StartToStart', constraint) > 0; + +i, rng.moveStart('character', -1)); return i; } function ieend (rng, constraint){ // returns the position (in character) of the end of rng within constraint. If it's not in constraint, returns 0 if it's before, length if it's after var len = constraint.text.replace(/r/g, '').length; // correct for IE's CrLf wierdness if (rng.compareEndPoints ('EndToEnd', constraint) >= 0) return len; // at or after the end if (rng.compareEndPoints ('EndToStart', constraint) <= 0) return 0; for (var i = 0; rng.compareEndPoints ('EndToStart', constraint) > 0; ++i, rng.moveEnd('character', -1)); return i; } // an input element in a standards document. "Native Range" is just the bounds array function InputRange(){} InputRange.prototype = new Range(); InputRange.prototype._nativeRange = function(bounds) { return bounds || [0, this.length()]; }; InputRange.prototype._nativeSelect = function (rng){ this._el.setSelectionRange(rng[0], rng[1]); }; InputRange.prototype._nativeSelection = function(){ return [this._el.selectionStart, this._el.selectionEnd]; }; InputRange.prototype._nativeGetText = function(rng){ return this._el.value.substring(rng[0], rng[1]); }; InputRange.prototype._nativeSetText = function(text, rng){ var val = this._el.value; this._el.value = val.substring(0, rng[0]) + text + val.substring(rng[1]); }; InputRange.prototype._nativeEOL = function(){ this.text('n'); }; function W3CRange(){} W3CRange.prototype = new Range(); W3CRange.prototype._nativeRange = function (bounds){ var rng = document.createRange(); rng.selectNodeContents(this._el); if (bounds){ w3cmoveBoundary (rng, bounds[0], true, this._el); rng.collapse (true); w3cmoveBoundary (rng, bounds[1]-bounds[0], false, this._el); } return rng; }; W3CRange.prototype._nativeSelect = function (rng){ window.getSelection().removeAllRanges(); window.getSelection().addRange (rng); }; W3CRange.prototype._nativeSelection = function (){
  • 5. // returns [start, end] for the selection constrained to be in element var rng = this._nativeRange(); // range of the element to constrain to if (window.getSelection().rangeCount == 0) return [this.length(), this.length()]; // append to the end var sel = window.getSelection().getRangeAt(0); return [ w3cstart(sel, rng), w3cend (sel, rng) ]; }; W3CRange.prototype._nativeGetText = function (rng){ return rng.toString(); }; W3CRange.prototype._nativeSetText = function (text, rng){ rng.deleteContents(); rng.insertNode (document.createTextNode(text)); this._el.normalize(); // merge the text with the surrounding text }; W3CRange.prototype._nativeEOL = function(){ var rng = this._nativeRange(this.bounds()); rng.deleteContents(); var br = document.createElement('br'); br.setAttribute ('_moz_dirty', ''); // for Firefox rng.insertNode (br); rng.insertNode (document.createTextNode('n')); rng.collapse (false); }; // W3C internals function nextnode (node, root){ // in-order traversal // we've already visited node, so get kids then siblings if (node.firstChild) return node.firstChild; if (node.nextSibling) return node.nextSibling; if (node===root) return null; while (node.parentNode){ // get uncles node = node.parentNode; if (node == root) return null; if (node.nextSibling) return node.nextSibling; } return null; } function w3cmoveBoundary (rng, n, bStart, el){ // move the boundary (bStart == true ? start : end) n characters forward, up to the end of element el. Forward only! // if the start is moved after the end, then an exception is raised if (n <= 0) return; var node = rng[bStart ? 'startContainer' : 'endContainer']; if (node.nodeType == 3){ // we may be starting somewhere into the text n += rng[bStart ? 'startOffset' : 'endOffset']; } while (node){ if (node.nodeType == 3){ if (n <= node.nodeValue.length){ rng[bStart ? 'setStart' : 'setEnd'](node, n); // special case: if we end next to a <br>, include that node. if (n == node.nodeValue.length){ // skip past zero-length text nodes for (var next = nextnode (node, el); next && next.nodeType==3 && next.nodeValue.length == 0; next = nextnode(next, el)){
  • 6. rng[bStart ? 'setStartAfter' : 'setEndAfter'](next); } if (next && next.nodeType == 1 && next.nodeName == "BR") rng[bStart ? 'setStartAfter' : 'setEndAfter'](next); } return; }else{ rng[bStart ? 'setStartAfter' : 'setEndAfter'](node); // skip past this one n -= node.nodeValue.length; // and eat these characters } } node = nextnode (node, el); } } var START_TO_START = 0; // from the w3c definitions var START_TO_END = 1; var END_TO_END = 2; var END_TO_START = 3; // from the Mozilla documentation, for range.compareBoundaryPoints(how, sourceRange) // -1, 0, or 1, indicating whether the corresponding boundary-point of range is respectively before, equal to, or after the corresponding boundary-point of sourceRange. // * Range.END_TO_END compares the end boundary-point of sourceRange to the end boundary-point of range. // * Range.END_TO_START compares the end boundary-point of sourceRange to the start boundary-point of range. // * Range.START_TO_END compares the start boundary-point of sourceRange to the end boundary-point of range. // * Range.START_TO_START compares the start boundary-point of sourceRange to the start boundary-point of range. function w3cstart(rng, constraint){ if (rng.compareBoundaryPoints (START_TO_START, constraint) <= 0) return 0; // at or before the beginning if (rng.compareBoundaryPoints (END_TO_START, constraint) >= 0) return constraint.toString().length; rng = rng.cloneRange(); // don't change the original rng.setEnd (constraint.endContainer, constraint.endOffset); // they now end at the same place return constraint.toString().length - rng.toString().length; } function w3cend (rng, constraint){ if (rng.compareBoundaryPoints (END_TO_END, constraint) >= 0) return constraint.toString().length; // at or after the end if (rng.compareBoundaryPoints (START_TO_END, constraint) <= 0) return 0; rng = rng.cloneRange(); // don't change the original rng.setStart (constraint.startContainer, constraint.startOffset); // they now start at the same place return rng.toString().length; } function NothingRange(){} NothingRange.prototype = new Range(); NothingRange.prototype._nativeRange = function(bounds) { return bounds || [0,this.length()]; }; NothingRange.prototype._nativeSelect = function (rng){ // do nothing }; NothingRange.prototype._nativeSelection = function(){ return [0,0]; }; NothingRange.prototype._nativeGetText = function (rng){
  • 7. return this._el[this._textProp].substring(rng[0], rng[1]); }; NothingRange.prototype._nativeSetText = function (text, rng){ var val = this._el[this._textProp]; this._el[this._textProp] = val.substring(0, rng[0]) + text + val.substring(rng[1]); }; NothingRange.prototype._nativeEOL = function(){ this.text('n'); }; })();