2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
\r
3 * Copyright (C) 2003-2009 Frederico Caldeira Knabben
\r
5 * == BEGIN LICENSE ==
\r
7 * Licensed under the terms of any of the following licenses at your
\r
10 * - GNU General Public License Version 2 or later (the "GPL")
\r
11 * http://www.gnu.org/licenses/gpl.html
\r
13 * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
\r
14 * http://www.gnu.org/licenses/lgpl.html
\r
16 * - Mozilla Public License Version 1.1 or later (the "MPL")
\r
17 * http://www.mozilla.org/MPL/MPL-1.1.html
\r
21 * This class partially implements the W3C DOM Range for browser that don't
\r
22 * support the standards (like IE):
\r
23 * http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html
\r
26 var FCKW3CRange = function( parentDocument )
\r
28 this._Document = parentDocument ;
\r
30 this.startContainer = null ;
\r
31 this.startOffset = null ;
\r
32 this.endContainer = null ;
\r
33 this.endOffset = null ;
\r
34 this.collapsed = true ;
\r
37 FCKW3CRange.CreateRange = function( parentDocument )
\r
39 // We could opt to use the Range implementation of the browsers. The problem
\r
40 // is that every browser have different bugs on their implementations,
\r
41 // mostly related to different interpretations of the W3C specifications.
\r
42 // So, for now, let's use our implementation and pray for browsers fixings
\r
43 // soon. Otherwise will go crazy on trying to find out workarounds.
\r
45 // Get the browser implementation of the range, if available.
\r
46 if ( parentDocument.createRange )
\r
48 var range = parentDocument.createRange() ;
\r
49 if ( typeof( range.startContainer ) != 'undefined' )
\r
53 return new FCKW3CRange( parentDocument ) ;
\r
56 FCKW3CRange.CreateFromRange = function( parentDocument, sourceRange )
\r
58 var range = FCKW3CRange.CreateRange( parentDocument ) ;
\r
59 range.setStart( sourceRange.startContainer, sourceRange.startOffset ) ;
\r
60 range.setEnd( sourceRange.endContainer, sourceRange.endOffset ) ;
\r
64 FCKW3CRange.prototype =
\r
67 _UpdateCollapsed : function()
\r
69 this.collapsed = ( this.startContainer == this.endContainer && this.startOffset == this.endOffset ) ;
\r
72 // W3C requires a check for the new position. If it is after the end
\r
73 // boundary, the range should be collapsed to the new start. It seams we
\r
74 // will not need this check for our use of this class so we can ignore it for now.
\r
75 setStart : function( refNode, offset )
\r
77 this.startContainer = refNode ;
\r
78 this.startOffset = offset ;
\r
80 if ( !this.endContainer )
\r
82 this.endContainer = refNode ;
\r
83 this.endOffset = offset ;
\r
86 this._UpdateCollapsed() ;
\r
89 // W3C requires a check for the new position. If it is before the start
\r
90 // boundary, the range should be collapsed to the new end. It seams we
\r
91 // will not need this check for our use of this class so we can ignore it for now.
\r
92 setEnd : function( refNode, offset )
\r
94 this.endContainer = refNode ;
\r
95 this.endOffset = offset ;
\r
97 if ( !this.startContainer )
\r
99 this.startContainer = refNode ;
\r
100 this.startOffset = offset ;
\r
103 this._UpdateCollapsed() ;
\r
106 setStartAfter : function( refNode )
\r
108 this.setStart( refNode.parentNode, FCKDomTools.GetIndexOf( refNode ) + 1 ) ;
\r
111 setStartBefore : function( refNode )
\r
113 this.setStart( refNode.parentNode, FCKDomTools.GetIndexOf( refNode ) ) ;
\r
116 setEndAfter : function( refNode )
\r
118 this.setEnd( refNode.parentNode, FCKDomTools.GetIndexOf( refNode ) + 1 ) ;
\r
121 setEndBefore : function( refNode )
\r
123 this.setEnd( refNode.parentNode, FCKDomTools.GetIndexOf( refNode ) ) ;
\r
126 collapse : function( toStart )
\r
130 this.endContainer = this.startContainer ;
\r
131 this.endOffset = this.startOffset ;
\r
135 this.startContainer = this.endContainer ;
\r
136 this.startOffset = this.endOffset ;
\r
139 this.collapsed = true ;
\r
142 selectNodeContents : function( refNode )
\r
144 this.setStart( refNode, 0 ) ;
\r
145 this.setEnd( refNode, refNode.nodeType == 3 ? refNode.data.length : refNode.childNodes.length ) ;
\r
148 insertNode : function( newNode )
\r
150 var startContainer = this.startContainer ;
\r
151 var startOffset = this.startOffset ;
\r
153 // If we are in a text node.
\r
154 if ( startContainer.nodeType == 3 )
\r
156 startContainer.splitText( startOffset ) ;
\r
158 // Check if it is necessary to update the end boundary.
\r
159 if ( startContainer == this.endContainer )
\r
160 this.setEnd( startContainer.nextSibling, this.endOffset - this.startOffset ) ;
\r
162 // Insert the new node it after the text node.
\r
163 FCKDomTools.InsertAfterNode( startContainer, newNode ) ;
\r
169 // Simply insert the new node before the current start node.
\r
170 startContainer.insertBefore( newNode, startContainer.childNodes[ startOffset ] || null ) ;
\r
172 // Check if it is necessary to update the end boundary.
\r
173 if ( startContainer == this.endContainer )
\r
176 this.collapsed = false ;
\r
181 deleteContents : function()
\r
183 if ( this.collapsed )
\r
186 this._ExecContentsAction( 0 ) ;
\r
189 extractContents : function()
\r
191 var docFrag = new FCKDocumentFragment( this._Document ) ;
\r
193 if ( !this.collapsed )
\r
194 this._ExecContentsAction( 1, docFrag ) ;
\r
199 // The selection may be lost when cloning (due to the splitText() call).
\r
200 cloneContents : function()
\r
202 var docFrag = new FCKDocumentFragment( this._Document ) ;
\r
204 if ( !this.collapsed )
\r
205 this._ExecContentsAction( 2, docFrag ) ;
\r
210 _ExecContentsAction : function( action, docFrag )
\r
212 var startNode = this.startContainer ;
\r
213 var endNode = this.endContainer ;
\r
215 var startOffset = this.startOffset ;
\r
216 var endOffset = this.endOffset ;
\r
218 var removeStartNode = false ;
\r
219 var removeEndNode = false ;
\r
221 // Check the start and end nodes and make the necessary removals or changes.
\r
223 // Start from the end, otherwise DOM mutations (splitText) made in the
\r
224 // start boundary may interfere on the results here.
\r
226 // For text containers, we must simply split the node and point to the
\r
227 // second part. The removal will be handled by the rest of the code .
\r
228 if ( endNode.nodeType == 3 )
\r
229 endNode = endNode.splitText( endOffset ) ;
\r
232 // If the end container has children and the offset is pointing
\r
233 // to a child, then we should start from it.
\r
234 if ( endNode.childNodes.length > 0 )
\r
236 // If the offset points after the last node.
\r
237 if ( endOffset > endNode.childNodes.length - 1 )
\r
239 // Let's create a temporary node and mark it for removal.
\r
240 endNode = FCKDomTools.InsertAfterNode( endNode.lastChild, this._Document.createTextNode('') ) ;
\r
241 removeEndNode = true ;
\r
244 endNode = endNode.childNodes[ endOffset ] ;
\r
248 // For text containers, we must simply split the node. The removal will
\r
249 // be handled by the rest of the code .
\r
250 if ( startNode.nodeType == 3 )
\r
252 startNode.splitText( startOffset ) ;
\r
254 // In cases the end node is the same as the start node, the above
\r
255 // splitting will also split the end, so me must move the end to
\r
256 // the second part of the split.
\r
257 if ( startNode == endNode )
\r
258 endNode = startNode.nextSibling ;
\r
262 // If the start container has children and the offset is pointing
\r
263 // to a child, then we should start from its previous sibling.
\r
265 // If the offset points to the first node, we don't have a
\r
266 // sibling, so let's use the first one, but mark it for removal.
\r
267 if ( startOffset == 0 )
\r
269 // Let's create a temporary node and mark it for removal.
\r
270 startNode = startNode.insertBefore( this._Document.createTextNode(''), startNode.firstChild ) ;
\r
271 removeStartNode = true ;
\r
273 else if ( startOffset > startNode.childNodes.length - 1 )
\r
275 // Let's create a temporary node and mark it for removal.
\r
276 startNode = startNode.appendChild( this._Document.createTextNode('') ) ;
\r
277 removeStartNode = true ;
\r
280 startNode = startNode.childNodes[ startOffset ].previousSibling ;
\r
283 // Get the parent nodes tree for the start and end boundaries.
\r
284 var startParents = FCKDomTools.GetParents( startNode ) ;
\r
285 var endParents = FCKDomTools.GetParents( endNode ) ;
\r
287 // Compare them, to find the top most siblings.
\r
288 var i, topStart, topEnd ;
\r
290 for ( i = 0 ; i < startParents.length ; i++ )
\r
292 topStart = startParents[i] ;
\r
293 topEnd = endParents[i] ;
\r
295 // The compared nodes will match until we find the top most
\r
296 // siblings (different nodes that have the same parent).
\r
297 // "i" will hold the index in the parents array for the top
\r
299 if ( topStart != topEnd )
\r
303 var clone, levelStartNode, levelClone, currentNode, currentSibling ;
\r
306 clone = docFrag.RootNode ;
\r
308 // Remove all successive sibling nodes for every node in the
\r
309 // startParents tree.
\r
310 for ( var j = i ; j < startParents.length ; j++ )
\r
312 levelStartNode = startParents[j] ;
\r
314 // For Extract and Clone, we must clone this level.
\r
315 if ( clone && levelStartNode != startNode ) // action = 0 = Delete
\r
316 levelClone = clone.appendChild( levelStartNode.cloneNode( levelStartNode == startNode ) ) ;
\r
318 currentNode = levelStartNode.nextSibling ;
\r
320 while( currentNode )
\r
322 // Stop processing when the current node matches a node in the
\r
323 // endParents tree or if it is the endNode.
\r
324 if ( currentNode == endParents[j] || currentNode == endNode )
\r
327 // Cache the next sibling.
\r
328 currentSibling = currentNode.nextSibling ;
\r
330 // If cloning, just clone it.
\r
331 if ( action == 2 ) // 2 = Clone
\r
332 clone.appendChild( currentNode.cloneNode( true ) ) ;
\r
335 // Both Delete and Extract will remove the node.
\r
336 currentNode.parentNode.removeChild( currentNode ) ;
\r
338 // When Extracting, move the removed node to the docFrag.
\r
339 if ( action == 1 ) // 1 = Extract
\r
340 clone.appendChild( currentNode ) ;
\r
343 currentNode = currentSibling ;
\r
347 clone = levelClone ;
\r
351 clone = docFrag.RootNode ;
\r
353 // Remove all previous sibling nodes for every node in the
\r
354 // endParents tree.
\r
355 for ( var k = i ; k < endParents.length ; k++ )
\r
357 levelStartNode = endParents[k] ;
\r
359 // For Extract and Clone, we must clone this level.
\r
360 if ( action > 0 && levelStartNode != endNode ) // action = 0 = Delete
\r
361 levelClone = clone.appendChild( levelStartNode.cloneNode( levelStartNode == endNode ) ) ;
\r
363 // The processing of siblings may have already been done by the parent.
\r
364 if ( !startParents[k] || levelStartNode.parentNode != startParents[k].parentNode )
\r
366 currentNode = levelStartNode.previousSibling ;
\r
368 while( currentNode )
\r
370 // Stop processing when the current node matches a node in the
\r
371 // startParents tree or if it is the startNode.
\r
372 if ( currentNode == startParents[k] || currentNode == startNode )
\r
375 // Cache the next sibling.
\r
376 currentSibling = currentNode.previousSibling ;
\r
378 // If cloning, just clone it.
\r
379 if ( action == 2 ) // 2 = Clone
\r
380 clone.insertBefore( currentNode.cloneNode( true ), clone.firstChild ) ;
\r
383 // Both Delete and Extract will remove the node.
\r
384 currentNode.parentNode.removeChild( currentNode ) ;
\r
386 // When Extracting, mode the removed node to the docFrag.
\r
387 if ( action == 1 ) // 1 = Extract
\r
388 clone.insertBefore( currentNode, clone.firstChild ) ;
\r
391 currentNode = currentSibling ;
\r
396 clone = levelClone ;
\r
399 if ( action == 2 ) // 2 = Clone.
\r
401 // No changes in the DOM should be done, so fix the split text (if any).
\r
403 var startTextNode = this.startContainer ;
\r
404 if ( startTextNode.nodeType == 3 )
\r
406 startTextNode.data += startTextNode.nextSibling.data ;
\r
407 startTextNode.parentNode.removeChild( startTextNode.nextSibling ) ;
\r
410 var endTextNode = this.endContainer ;
\r
411 if ( endTextNode.nodeType == 3 && endTextNode.nextSibling )
\r
413 endTextNode.data += endTextNode.nextSibling.data ;
\r
414 endTextNode.parentNode.removeChild( endTextNode.nextSibling ) ;
\r
419 // Collapse the range.
\r
421 // If a node has been partially selected, collapse the range between
\r
422 // topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs).
\r
423 if ( topStart && topEnd && ( startNode.parentNode != topStart.parentNode || endNode.parentNode != topEnd.parentNode ) )
\r
425 var endIndex = FCKDomTools.GetIndexOf( topEnd ) ;
\r
427 // If the start node is to be removed, we must correct the
\r
428 // index to reflect the removal.
\r
429 if ( removeStartNode && topEnd.parentNode == startNode.parentNode )
\r
432 this.setStart( topEnd.parentNode, endIndex ) ;
\r
435 // Collapse it to the start.
\r
436 this.collapse( true ) ;
\r
439 // Cleanup any marked node.
\r
440 if( removeStartNode )
\r
441 startNode.parentNode.removeChild( startNode ) ;
\r
443 if( removeEndNode && endNode.parentNode )
\r
444 endNode.parentNode.removeChild( endNode ) ;
\r
447 cloneRange : function()
\r
449 return FCKW3CRange.CreateFromRange( this._Document, this ) ;
\r