root/branches/vox-28/common/SelectionRange.js @ 163

Revision 163, 10.1 kB (checked in by ydnar, 3 years ago)

bugid:49744; hate IE TextRange::compareEndPoints

  • 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 offsets = {};
109        var steps = 0;
110        var broken = false;
111       
112        /* this breaks if the user selects all, where the selection endpoint is at the end of body */
113        /* hence storing previously-visited offsets */
114       
115        while( test = range2.compareEndPoints( compareType, range ) ) {
116            if( test < 0 ) {
117                range2.move( "character", delta );
118                offset += delta;
119            } else {
120                range2.move( "character", -delta );
121                offset -= delta;
122            }
123           
124            delta = max( 1, finiteInt( delta * 0.5 ) );
125           
126            /* visited this offset before? */
127            if( offsets[ offset ] === offset ) {
128                broken = true;
129                break;
130            }
131            offsets[ offset ] = offset;
132           
133            /* infinite loop bug */
134            steps++;
135            if( steps > 1000 || (offset < 0 || offset > length + 1) ) {
136                broken = true;
137                break;
138            }
139        }
140       
141        /*
142        if( broken )
143            log( "BROKEN: " + parent.tagName + " " + length + " " + compareType + " " + test + " " + delta + " " + offset );
144        else
145            log( "GOOD: " + parent.tagName + " " + offset );
146        */
147       
148        return DOM.Proxy.findTextPosition( parent, offset );
149    },
150   
151   
152    setStart: function( startContainer, startOffset ) {
153        this.startContainer = startContainer;
154        this.startOffset = startOffset;
155    },
156   
157   
158    setEnd: function( endContainer, endOffset ) {
159        this.endContainer = endContainer;
160        this.endOffset = endOffset;
161    },
162   
163   
164    collapse: function( toStart ) {
165        if( toStart )
166            this.setEnd( this.startContainer, this.startOffset );
167        else
168            this.setStart( this.endContainer, this.endOffset );
169    },
170   
171   
172    getCommonAncestorContainer: function() {
173        if( this.startContainer == this.endContainer )
174            return this.startContainer;
175        var start = DOM.getAncestors( this.startContainer, true );
176        var end = DOM.getAncestors( this.endContainer, true );
177        var common = null;
178        for( i = 1; i <= start.length && i <= end.length; i++ ) {
179            if( start[ start.length - i ] == end[ end.length - i ] )
180                common = start[ start.length - i ];
181        }
182        return common;
183    },
184   
185   
186    getNodes: function() {
187        var nodes = [];
188        if( !this.startContainer )
189            return nodes;
190        nodes.push( this.startContainer );
191        if( this.startContainer === this.endContainer &&
192            (this.startOffset == this.endOffset || !this.startContainer.firstChild) )
193            return nodes;
194        var proxy = new DOM.Proxy( this.startContainer );
195        while( proxy.getNextNode() && proxy.node != this.endContainer )
196            nodes.push( proxy.node );
197        return nodes;
198    },
199   
200   
201    getTextNodes: function() {
202        var proxy = new DOM.Proxy( this.startContainer );
203        var nodes = proxy.node.nodeType == Node.TEXT_NODE ? [ proxy.node ] : [];
204        while( proxy.node != this.endContainer ) {
205            proxy.getNextNode();
206            if( proxy.node.nodeType == Node.TEXT_NODE )
207                nodes.push( proxy.node );
208        }
209        return nodes;
210    },
211   
212   
213    surround: function( tagName, attributes ) {
214        // fixme: make this work with non-text nodes
215        if( this.startContainer.nodeType != Node.TEXT_NODE ||
216            this.endContainer.nodeType != Node.TEXT_NODE )
217            return;
218       
219        var nodes = this.getTextNodes();
220        var last;
221        for( var i = 0; i < nodes.length; i++ ) {
222            var node = nodes[ i ];
223            var startOffset = i == 0
224                ? this.startOffset
225                : 0;
226            var endOffset = i == (nodes.length - 1)
227                ? this.endOffset
228                : node.nodeValue.length
229            var inside = this.surroundTextNode( node, startOffset, endOffset, tagName, attributes );
230            if( i == 0 )
231                this.setStart( inside, 0 );
232            if( i == (nodes.length - 1) )
233                this.setEnd( inside, inside.nodeValue.length );
234        }
235       
236        return last;
237    },
238   
239   
240    surroundTextNode: function( node, startOffset, endOffset, tagName, attributes ) {
241        var document = this.startContainer.ownerDocument;
242       
243        var parent = node.parentNode;
244        if( endOffset < startOffset ) {
245            var temp = endOffset;
246            endOffset = startOffset;
247            startOffset = temp;
248        }
249       
250        var element = document.createElement( tagName );
251        for( var attribute in attributes ) {
252            if( attributes.hasOwnProperty( attribute ) ) {
253                if( attribute == "class" )
254                    element.className = attributes[ attribute ];
255                else
256                    element.setAttribute( attribute, attributes[ attribute ] );
257            }
258        }
259       
260        var value = node.nodeValue;
261       
262        var inner = document.createTextNode( value.substring( startOffset, endOffset ) );
263        element.appendChild( inner );
264        parent.replaceChild( element, node );
265       
266        if( startOffset > 0 ) {
267            var before = document.createTextNode( value.substring( 0, startOffset ) );
268            parent.insertBefore( before, element );
269        }
270       
271        if( endOffset < value.length ) {
272            var after = document.createTextNode( value.substring( endOffset, value.length ) );
273            parent.insertBefore( after, element.nextSibling );
274        }
275       
276        return inner;
277    },
278   
279   
280    replaceText: function( value ) {
281        var offset = 0;
282        var nodes = this.getTextNodes();
283        for( var i = 0; i < nodes.length; i++ ) {
284            var node = nodes[ i ];
285            var nodeValue = node.nodeValue;
286           
287            if( offset >= value.length && value.length > 0 )
288                value = "";
289           
290            if( node === this.startContainer ) {
291                var delta = nodeValue.length - this.startOffset;
292                if( delta > (value.length - offset) ||
293                    node === this.endContainer )
294                    delta = value.length - offset;
295                node.nodeValue = nodeValue.substring( 0, this.startOffset )
296                    + value.substr( offset, delta );
297                offset += delta;
298            } else if( node === this.endContainer ) {
299                node.nodeValue = value.substring( offset, value.length ) +
300                    nodeValue.substring( this.endOffset, nodeValue.length );
301                offset = value.length;
302            } else {
303                var delta = nodeValue.length;
304                node.nodeValue = value.substr( offset, delta );
305                offset += delta;
306            }
307        }
308    }
309} );
Note: See TracBrowser for help on using the browser.