root/trunk/common/SelectionRange.js @ 159

Revision 159, 9.5 kB (checked in by ydnar, 3 years ago)

added line

  • Property svn:keywords set to Id
Line 
1/*
2SelectionRange Class - Copyright 2005 Six Apart
3$Id$
4*/
5
6
7SelectionRange = new Class( Object, {
8    init: function() {
9        // mozilla selection
10        if( arguments[ 0 ].getRangeAt )
11            this.fromMozillaSelection( arguments[ 0 ] );
12       
13        // khtml (safari) selection
14        else if( arguments[ 0 ].setBaseAndExtent )
15            this.fromKHTMLSelection( arguments[ 0 ] );
16       
17        // w3c range
18        else if( arguments[ 0 ].selectNodeContents )
19            this.fromRange( arguments[ 0 ] );
20       
21        // internet explorer selection
22        else if( arguments[ 0 ].createRange )
23            this.fromIESelection( arguments[ 0 ] );
24       
25        // internet explorer control range
26        else if( arguments[ 0 ].addElement )
27            this.fromIEControlRange( arguments[ 0 ] );
28       
29        // internet explorer text range
30        else if( arguments[ 0 ].compareEndPoints )
31            this.fromIETextRange( arguments[ 0 ] );
32       
33        // 4-argument form
34        else {
35            this.startContainer = arguments[ 0 ];
36            this.startOffset = arguments[ 1 ];
37            this.endContainer = arguments[ 2 ] || arguments[ 0 ];
38            this.endOffset = arguments[ 3 ] || arguments[ 1 ];
39        }
40    },
41   
42   
43    fromMozillaSelection: function( selection ) {
44        var range = selection.getRangeAt( 0 );
45        this.fromRange( range );
46    },
47   
48   
49    fromKHTMLSelection: function( selection ) {
50        this.startContainer = selection.baseNode;
51        this.startOffset = selection.baseOffset;
52        this.endContainer = selection.extentNode;
53        this.endOffset = selection.extentOffset;
54    },
55   
56   
57    fromIESelection: function( selection ) {
58        var range = selection.createRange();
59        this.fromIERange( range );
60    },
61   
62   
63    fromRange: function( range ) {
64        this.startContainer = range.startContainer;
65        this.startOffset = range.startOffset;
66        this.endContainer = range.endContainer;
67        this.endOffset = range.endOffset;
68    },
69   
70   
71    fromIERange: function( range ) {
72        if( range.addElement )
73            this.fromIEControlRange( range );
74        else if( range.compareEndPoints )
75            this.fromIETextRange( range );
76    },
77   
78   
79    fromIEControlRange: function( range ) {
80        // fixme: this is kinda broken
81        this.startContainer = range.item( 0 );
82        this.startOffset = 0;
83        this.endContainer = range.item( range.length - 1 );
84        this.endOffset = 0;
85    },
86   
87   
88    fromIETextRange: function( range ) {
89        var position = this.findIETextRangePosition( range, "StartToStart" );
90        this.startContainer = position.node;
91        this.startOffset = position.offset;
92       
93        position = this.findIETextRangePosition( range, "EndToEnd" );
94        this.endContainer = position.node;
95        this.endOffset = position.offset;
96    },
97   
98   
99    findIETextRangePosition: function( range, compareType ) {
100        var range2 = range.duplicate();
101        range2.collapse( true );
102        var parent = range2.parentElement();
103        range2.moveToElementText( parent );
104        var length = range2.text.length;
105        var delta = max( 1, finiteInt( length * 0.5 ) );
106        range2.collapse( true );
107        var offset = 0;
108        var steps = 0;
109       
110        // bail after 1k iterations in case of borkage
111        while( (test = range2.compareEndPoints( compareType, range )) != 0 ) {
112            if( test < 0 ) {
113                range2.move( "character", delta );
114                offset += delta;
115            } else {
116                range2.move( "character", -delta );
117                offset -= delta;
118            }
119            delta = max( 1, finiteInt( delta * 0.5 ) );
120            steps++;
121            if( steps > 1000 )
122                throw "unable to find textrange endpoint in " + steps + " steps";
123        }
124       
125        // this breaks if the user selects all, where the selection endpoint is at the
126        // end of body
127       
128        //log( "steps: " + steps );
129        return DOM.Proxy.findTextPosition( parent, offset );
130    },
131   
132   
133    setStart: function( startContainer, startOffset ) {
134        this.startContainer = startContainer;
135        this.startOffset = startOffset;
136    },
137   
138   
139    setEnd: function( endContainer, endOffset ) {
140        this.endContainer = endContainer;
141        this.endOffset = endOffset;
142    },
143   
144   
145    collapse: function( toStart ) {
146        if( toStart )
147            this.setEnd( this.startContainer, this.startOffset );
148        else
149            this.setStart( this.endContainer, this.endOffset );
150    },
151   
152   
153    getCommonAncestorContainer: function() {
154        if( this.startContainer == this.endContainer )
155            return this.startContainer;
156        var start = DOM.getAncestors( this.startContainer, true );
157        var end = DOM.getAncestors( this.endContainer, true );
158        var common = null;
159        for( i = 1; i <= start.length && i <= end.length; i++ ) {
160            if( start[ start.length - i ] == end[ end.length - i ] )
161                common = start[ start.length - i ];
162        }
163        return common;
164    },
165   
166   
167    getNodes: function() {
168        var nodes = [];
169        if( !this.startContainer )
170            return nodes;
171        nodes.push( this.startContainer );
172        if( this.startContainer === this.endContainer &&
173            (this.startOffset == this.endOffset || !this.startContainer.firstChild) )
174            return nodes;
175        var proxy = new DOM.Proxy( this.startContainer );
176        while( proxy.getNextNode() && proxy.node != this.endContainer )
177            nodes.push( proxy.node );
178        return nodes;
179    },
180   
181   
182    getTextNodes: function() {
183        var proxy = new DOM.Proxy( this.startContainer );
184        var nodes = proxy.node.nodeType == Node.TEXT_NODE ? [ proxy.node ] : [];
185        while( proxy.node != this.endContainer ) {
186            proxy.getNextNode();
187            if( proxy.node.nodeType == Node.TEXT_NODE )
188                nodes.push( proxy.node );
189        }
190        return nodes;
191    },
192   
193   
194    surround: function( tagName, attributes ) {
195        // fixme: make this work with non-text nodes
196        if( this.startContainer.nodeType != Node.TEXT_NODE ||
197            this.endContainer.nodeType != Node.TEXT_NODE )
198            return;
199       
200        var nodes = this.getTextNodes();
201        var last;
202        for( var i = 0; i < nodes.length; i++ ) {
203            var node = nodes[ i ];
204            var startOffset = i == 0
205                ? this.startOffset
206                : 0;
207            var endOffset = i == (nodes.length - 1)
208                ? this.endOffset
209                : node.nodeValue.length
210            var inside = this.surroundTextNode( node, startOffset, endOffset, tagName, attributes );
211            if( i == 0 )
212                this.setStart( inside, 0 );
213            if( i == (nodes.length - 1) )
214                this.setEnd( inside, inside.nodeValue.length );
215        }
216       
217        return last;
218    },
219   
220   
221    surroundTextNode: function( node, startOffset, endOffset, tagName, attributes ) {
222        var document = this.startContainer.ownerDocument;
223       
224        var parent = node.parentNode;
225        if( endOffset < startOffset ) {
226            var temp = endOffset;
227            endOffset = startOffset;
228            startOffset = temp;
229        }
230       
231        var element = document.createElement( tagName );
232        for( var attribute in attributes ) {
233            if( attributes.hasOwnProperty( attribute ) ) {
234                if( attribute == "class" )
235                    element.className = attributes[ attribute ];
236                else
237                    element.setAttribute( attribute, attributes[ attribute ] );
238            }
239        }
240       
241        var value = node.nodeValue;
242       
243        var inner = document.createTextNode( value.substring( startOffset, endOffset ) );
244        element.appendChild( inner );
245        parent.replaceChild( element, node );
246       
247        if( startOffset > 0 ) {
248            var before = document.createTextNode( value.substring( 0, startOffset ) );
249            parent.insertBefore( before, element );
250        }
251       
252        if( endOffset < value.length ) {
253            var after = document.createTextNode( value.substring( endOffset, value.length ) );
254            parent.insertBefore( after, element.nextSibling );
255        }
256       
257        return inner;
258    },
259   
260   
261    replaceText: function( value ) {
262        var offset = 0;
263        var nodes = this.getTextNodes();
264        for( var i = 0; i < nodes.length; i++ ) {
265            var node = nodes[ i ];
266            var nodeValue = node.nodeValue;
267           
268            if( offset >= value.length && value.length > 0 )
269                value = "";
270           
271            if( node === this.startContainer ) {
272                var delta = nodeValue.length - this.startOffset;
273                if( delta > (value.length - offset) ||
274                    node === this.endContainer )
275                    delta = value.length - offset;
276                node.nodeValue = nodeValue.substring( 0, this.startOffset )
277                    + value.substr( offset, delta );
278                offset += delta;
279            } else if( node === this.endContainer ) {
280                node.nodeValue = value.substring( offset, value.length ) +
281                    nodeValue.substring( this.endOffset, nodeValue.length );
282                offset = value.length;
283            } else {
284                var delta = nodeValue.length;
285                node.nodeValue = value.substr( offset, delta );
286                offset += delta;
287            }
288        }
289    }
290} );
Note: See TracBrowser for help on using the browser.