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