2
* FCKeditor - The text editor for Internet - http://www.fckeditor.net
3
* Copyright (C) 2003-2010 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 implementation 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 cloning (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.
265
// If the offset points to the first node, we don't have a
266
// sibling, so let's use the first one, but mark it for removal.
267
if ( startOffset == 0 )
269
// Let's create a temporary node and mark it for removal.
270
startNode = startNode.insertBefore( this._Document.createTextNode(''), startNode.firstChild ) ;
271
removeStartNode = true ;
273
else if ( startOffset > startNode.childNodes.length - 1 )
275
// Let's create a temporary node and mark it for removal.
276
startNode = startNode.appendChild( this._Document.createTextNode('') ) ;
277
removeStartNode = true ;
280
startNode = startNode.childNodes[ startOffset ].previousSibling ;
283
// Get the parent nodes tree for the start and end boundaries.
284
var startParents = FCKDomTools.GetParents( startNode ) ;
285
var endParents = FCKDomTools.GetParents( endNode ) ;
287
// Compare them, to find the top most siblings.
288
var i, topStart, topEnd ;
290
for ( i = 0 ; i < startParents.length ; i++ )
292
topStart = startParents[i] ;
293
topEnd = endParents[i] ;
295
// The compared nodes will match until we find the top most
296
// siblings (different nodes that have the same parent).
297
// "i" will hold the index in the parents array for the top
299
if ( topStart != topEnd )
303
var clone, levelStartNode, levelClone, currentNode, currentSibling ;
306
clone = docFrag.RootNode ;
308
// Remove all successive sibling nodes for every node in the
309
// startParents tree.
310
for ( var j = i ; j < startParents.length ; j++ )
312
levelStartNode = startParents[j] ;
314
// For Extract and Clone, we must clone this level.
315
if ( clone && levelStartNode != startNode ) // action = 0 = Delete
316
levelClone = clone.appendChild( levelStartNode.cloneNode( levelStartNode == startNode ) ) ;
318
currentNode = levelStartNode.nextSibling ;
322
// Stop processing when the current node matches a node in the
323
// endParents tree or if it is the endNode.
324
if ( currentNode == endParents[j] || currentNode == endNode )
327
// Cache the next sibling.
328
currentSibling = currentNode.nextSibling ;
330
// If cloning, just clone it.
331
if ( action == 2 ) // 2 = Clone
332
clone.appendChild( currentNode.cloneNode( true ) ) ;
335
// Both Delete and Extract will remove the node.
336
currentNode.parentNode.removeChild( currentNode ) ;
338
// When Extracting, move the removed node to the docFrag.
339
if ( action == 1 ) // 1 = Extract
340
clone.appendChild( currentNode ) ;
343
currentNode = currentSibling ;
351
clone = docFrag.RootNode ;
353
// Remove all previous sibling nodes for every node in the
355
for ( var k = i ; k < endParents.length ; k++ )
357
levelStartNode = endParents[k] ;
359
// For Extract and Clone, we must clone this level.
360
if ( action > 0 && levelStartNode != endNode ) // action = 0 = Delete
361
levelClone = clone.appendChild( levelStartNode.cloneNode( levelStartNode == endNode ) ) ;
363
// The processing of siblings may have already been done by the parent.
364
if ( !startParents[k] || levelStartNode.parentNode != startParents[k].parentNode )
366
currentNode = levelStartNode.previousSibling ;
370
// Stop processing when the current node matches a node in the
371
// startParents tree or if it is the startNode.
372
if ( currentNode == startParents[k] || currentNode == startNode )
375
// Cache the next sibling.
376
currentSibling = currentNode.previousSibling ;
378
// If cloning, just clone it.
379
if ( action == 2 ) // 2 = Clone
380
clone.insertBefore( currentNode.cloneNode( true ), clone.firstChild ) ;
383
// Both Delete and Extract will remove the node.
384
currentNode.parentNode.removeChild( currentNode ) ;
386
// When Extracting, mode the removed node to the docFrag.
387
if ( action == 1 ) // 1 = Extract
388
clone.insertBefore( currentNode, clone.firstChild ) ;
391
currentNode = currentSibling ;
399
if ( action == 2 ) // 2 = Clone.
401
// No changes in the DOM should be done, so fix the split text (if any).
403
var startTextNode = this.startContainer ;
404
if ( startTextNode.nodeType == 3 )
406
startTextNode.data += startTextNode.nextSibling.data ;
407
startTextNode.parentNode.removeChild( startTextNode.nextSibling ) ;
410
var endTextNode = this.endContainer ;
411
if ( endTextNode.nodeType == 3 && endTextNode.nextSibling )
413
endTextNode.data += endTextNode.nextSibling.data ;
414
endTextNode.parentNode.removeChild( endTextNode.nextSibling ) ;
419
// Collapse the range.
421
// If a node has been partially selected, collapse the range between
422
// topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs).
423
if ( topStart && topEnd && ( startNode.parentNode != topStart.parentNode || endNode.parentNode != topEnd.parentNode ) )
425
var endIndex = FCKDomTools.GetIndexOf( topEnd ) ;
427
// If the start node is to be removed, we must correct the
428
// index to reflect the removal.
429
if ( removeStartNode && topEnd.parentNode == startNode.parentNode )
432
this.setStart( topEnd.parentNode, endIndex ) ;
435
// Collapse it to the start.
436
this.collapse( true ) ;
439
// Cleanup any marked node.
440
if( removeStartNode )
441
startNode.parentNode.removeChild( startNode ) ;
443
if( removeEndNode && endNode.parentNode )
444
endNode.parentNode.removeChild( endNode ) ;
447
cloneRange : function()
449
return FCKW3CRange.CreateFromRange( this._Document, this ) ;