Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2150 mathias 1
if(!dojo._hasResource["dijit._editor.range"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2
dojo._hasResource["dijit._editor.range"] = true;
3
dojo.provide("dijit._editor.range");
4
 
5
dijit.range={};
6
 
7
dijit.range.getIndex=function(/*DomNode*/node, /*DomNode*/parent){
8
//	dojo.profile.start("dijit.range.getIndex");
9
	var ret=[], retR=[];
10
	var stop = parent;
11
	var onode = node;
12
 
13
	while(node != stop){
14
		var i = 0;
15
		var pnode = node.parentNode, n;
16
		while(n=pnode.childNodes[i++]){
17
			if(n===node){
18
				--i;
19
				break;
20
			}
21
		}
22
		if(i>=pnode.childNodes.length){
23
			dojo.debug("Error finding index of a node in dijit.range.getIndex");
24
		}
25
		ret.unshift(i);
26
		retR.unshift(i-pnode.childNodes.length);
27
		node = pnode;
28
	}
29
 
30
	//normalized() can not be called so often to prevent
31
	//invalidating selection/range, so we have to detect
32
	//here that any text nodes in a row
33
	if(ret.length>0 && onode.nodeType==3){
34
		var n = onode.previousSibling;
35
		while(n && n.nodeType==3){
36
			ret[ret.length-1]--;
37
			n = n.previousSibling;
38
		}
39
		n = onode.nextSibling;
40
		while(n && n.nodeType==3){
41
			retR[retR.length-1]++;
42
			n = n.nextSibling;
43
		}
44
	}
45
//	dojo.profile.end("dijit.range.getIndex");
46
	return {o: ret, r:retR};
47
}
48
 
49
dijit.range.getNode = function(/*Array*/index, /*DomNode*/parent){
50
	if(!dojo.isArray(index) || index.length==0){
51
		return parent;
52
	}
53
	var node = parent;
54
//	if(!node)debugger
55
	dojo.every(index, function(i){
56
		if(i>=0&&i< node.childNodes.length){
57
			node = node.childNodes[i];
58
		}else{
59
			node = null;
60
			console.debug('Error: can not find node with index',index,'under parent node',parent );
61
			return false; //terminate dojo.every
62
		}
63
		return true; //carry on the every loop
64
	});
65
 
66
	return node;
67
}
68
 
69
dijit.range.getCommonAncestor = function(n1,n2,root){
70
	var getAncestors = function(n,root){
71
		var as=[];
72
		while(n){
73
			as.unshift(n);
74
			if(n!=root && n.tagName!='BODY'){
75
				n = n.parentNode;
76
			}else{
77
				break;
78
			}
79
		}
80
		return as;
81
	};
82
	var n1as = getAncestors(n1,root);
83
	var n2as = getAncestors(n2,root);
84
 
85
	var m = Math.min(n1as.length,n2as.length);
86
	var com = n1as[0]; //at least, one element should be in the array: the root (BODY by default)
87
	for(var i=1;i<m;i++){
88
		if(n1as[i]===n2as[i]){
89
			com = n1as[i]
90
		}else{
91
			break;
92
		}
93
	}
94
	return com;
95
}
96
 
97
dijit.range.getAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
98
	root = root || node.ownerDocument.body;
99
	while(node && node !== root){
100
		var name = node.nodeName.toUpperCase() ;
101
		if(regex.test(name)){
102
			return node;
103
		}
104
 
105
		node = node.parentNode;
106
	}
107
	return null;
108
}
109
 
110
dijit.range.BlockTagNames = /^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/;
111
dijit.range.getBlockAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
112
	root = root || node.ownerDocument.body;
113
	regex = regex || dijit.range.BlockTagNames;
114
	var block=null, blockContainer;
115
	while(node && node !== root){
116
		var name = node.nodeName.toUpperCase() ;
117
		if(!block && regex.test(name)){
118
			block = node;
119
		}
120
		if(!blockContainer && (/^(?:BODY|TD|TH|CAPTION)$/).test(name)){
121
			blockContainer = node;
122
		}
123
 
124
		node = node.parentNode;
125
	}
126
	return {blockNode:block, blockContainer:blockContainer || node.ownerDocument.body};
127
}
128
 
129
dijit.range.atBeginningOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
130
	var atBeginning = false;
131
	var offsetAtBeginning = (offset == 0);
132
	if(!offsetAtBeginning && node.nodeType==3){ //if this is a text node, check whether the left part is all space
133
		if(dojo.trim(node.nodeValue.substr(0,offset))==0){
134
			offsetAtBeginning = true;
135
		}
136
	}
137
	if(offsetAtBeginning){
138
		var cnode = node;
139
		atBeginning = true;
140
		while(cnode && cnode !== container){
141
			if(cnode.previousSibling){
142
				atBeginning = false;
143
				break;
144
			}
145
			cnode = cnode.parentNode;
146
		}
147
	}
148
	return atBeginning;
149
}
150
 
151
dijit.range.atEndOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
152
	var atEnd = false;
153
	var offsetAtEnd = (offset == (node.length || node.childNodes.length));
154
	if(!offsetAtEnd && node.nodeType==3){ //if this is a text node, check whether the right part is all space
155
		if(dojo.trim(node.nodeValue.substr(offset))==0){
156
			offsetAtEnd = true;
157
		}
158
	}
159
	if(offsetAtEnd){
160
		var cnode = node;
161
		atEnd = true;
162
		while(cnode && cnode !== container){
163
			if(cnode.nextSibling){
164
				atEnd = false;
165
				break;
166
			}
167
			cnode = cnode.parentNode;
168
		}
169
	}
170
	return atEnd;
171
}
172
 
173
dijit.range.adjacentNoneTextNode=function(startnode, next){
174
	var node = startnode;
175
	var len = (0-startnode.length) || 0;
176
	var prop = next?'nextSibling':'previousSibling';
177
	while(node){
178
		if(node.nodeType!=3){
179
			break;
180
		}
181
		len += node.length
182
		node = node[prop];
183
	}
184
	return [node,len];
185
}
186
 
187
dijit.range._w3c = Boolean(window['getSelection']);
188
dijit.range.create = function(){
189
	if(dijit.range._w3c){
190
		return document.createRange();
191
	}else{//IE
192
		return new dijit.range.W3CRange;
193
	}
194
}
195
 
196
dijit.range.getSelection = function(win, /*Boolean?*/ignoreUpdate){
197
	if(dijit.range._w3c){
198
		return win.getSelection();
199
	}else{//IE
200
		var id=win.__W3CRange;
201
		if(!id || !dijit.range.ie.cachedSelection[id]){
202
			var s = new dijit.range.ie.selection(win);
203
			//use win as the key in an object is not reliable, which
204
			//can leads to quite odd behaviors. thus we generate a
205
			//string and use it as a key in the cache
206
			id=(new Date).getTime();
207
			while(id in dijit.range.ie.cachedSelection){
208
				id=id+1;
209
			}
210
			id=String(id);
211
			dijit.range.ie.cachedSelection[id] = s;
212
		}else{
213
			var s = dijit.range.ie.cachedSelection[id];
214
		}
215
		if(!ignoreUpdate){
216
			s._getCurrentSelection();
217
		}
218
		return s;
219
	}
220
}
221
 
222
if(!dijit.range._w3c){
223
	dijit.range.ie={
224
		cachedSelection: {},
225
		selection: function(win){
226
			this._ranges = [];
227
			this.addRange = function(r, /*boolean*/internal){
228
				this._ranges.push(r);
229
				if(!internal){
230
					r._select();
231
				}
232
				this.rangeCount = this._ranges.length;
233
			};
234
			this.removeAllRanges = function(){
235
				//don't detach, the range may be used later
236
//				for(var i=0;i<this._ranges.length;i++){
237
//					this._ranges[i].detach();
238
//				}
239
				this._ranges = [];
240
				this.rangeCount = 0;
241
			};
242
			var _initCurrentRange = function(){
243
				var r = win.document.selection.createRange();
244
				var type=win.document.selection.type.toUpperCase();
245
				if(type == "CONTROL"){
246
					//TODO: multiple range selection(?)
247
					return new dijit.range.W3CRange(dijit.range.ie.decomposeControlRange(r));
248
				}else{
249
					return new dijit.range.W3CRange(dijit.range.ie.decomposeTextRange(r));
250
				}
251
			};
252
			this.getRangeAt = function(i){
253
				return this._ranges[i];
254
			};
255
			this._getCurrentSelection = function(){
256
				this.removeAllRanges();
257
				var r=_initCurrentRange();
258
				if(r){
259
					this.addRange(r, true);
260
				}
261
			};
262
		},
263
		decomposeControlRange: function(range){
264
			var firstnode = range.item(0), lastnode = range.item(range.length-1)
265
			var startContainer = firstnode.parentNode, endContainer = lastnode.parentNode;
266
			var startOffset = dijit.range.getIndex(firstnode, startContainer).o;
267
			var endOffset = dijit.range.getIndex(lastnode, endContainer).o+1;
268
			return [[startContainer, startOffset],[endContainer, endOffset]];
269
		},
270
		getEndPoint: function(range, end){
271
			var atmrange = range.duplicate();
272
			atmrange.collapse(!end);
273
			var cmpstr = 'EndTo' + (end?'End':'Start');
274
			var parentNode = atmrange.parentElement();
275
 
276
			var startnode, startOffset, lastNode;
277
			if(parentNode.childNodes.length>0){
278
				dojo.every(parentNode.childNodes, function(node,i){
279
					var calOffset;
280
					if(node.nodeType != 3){
281
						atmrange.moveToElementText(node);
282
 
283
						if(atmrange.compareEndPoints(cmpstr,range) > 0){
284
							startnode = node.previousSibling;
285
							if(lastNode && lastNode.nodeType == 3){
286
								//where share we put the start? in the text node or after?
287
								startnode = lastNode;
288
								calOffset = true;
289
							}else{
290
								startnode = parentNode;
291
								startOffset = i;
292
								return false;
293
							}
294
						}else{
295
							if(i==parentNode.childNodes.length-1){
296
								startnode = parentNode;
297
								startOffset = parentNode.childNodes.length;
298
								return false;
299
							}
300
						}
301
					}else{
302
						if(i==parentNode.childNodes.length-1){//at the end of this node
303
							startnode = node;
304
							calOffset = true;
305
						}
306
					}
307
		//			try{
308
						if(calOffset && startnode){
309
							var prevnode = dijit.range.adjacentNoneTextNode(startnode)[0];
310
							if(prevnode){
311
								startnode = prevnode.nextSibling;
312
							}else{
313
								startnode = parentNode.firstChild; //firstChild must be a text node
314
							}
315
							var prevnodeobj = dijit.range.adjacentNoneTextNode(startnode);
316
							prevnode = prevnodeobj[0];
317
							var lenoffset = prevnodeobj[1];
318
							if(prevnode){
319
								atmrange.moveToElementText(prevnode);
320
								atmrange.collapse(false);
321
							}else{
322
								atmrange.moveToElementText(parentNode);
323
							}
324
							atmrange.setEndPoint(cmpstr, range);
325
							startOffset = atmrange.text.length-lenoffset;
326
 
327
							return false;
328
						}
329
		//			}catch(e){ debugger }
330
					lastNode = node;
331
					return true;
332
				});
333
			}else{
334
				startnode = parentNode;
335
				startOffset = 0;
336
			}
337
 
338
			//if at the end of startnode and we are dealing with start container, then
339
			//move the startnode to nextSibling if it is a text node
340
			//TODO: do this for end container?
341
			if(!end && startnode.nodeType!=3 && startOffset == startnode.childNodes.length){
342
				if(startnode.nextSibling && startnode.nextSibling.nodeType==3){
343
					startnode = startnode.nextSibling;
344
					startOffset = 0;
345
				}
346
			}
347
			return [startnode, startOffset];
348
		},
349
		setEndPoint: function(range, container, offset){
350
			//text node
351
			var atmrange = range.duplicate();
352
			if(container.nodeType!=3){ //normal node
353
				atmrange.moveToElementText(container);
354
				atmrange.collapse(true);
355
				if(offset == container.childNodes.length){
356
					if(offset > 0){
357
						//a simple atmrange.collapse(false); won't work here:
358
						//although moveToElementText(node) is supposed to encompass the content of the node,
359
						//but when collapse to end, it is in fact after the ending tag of node (collapse to start
360
						//is after the begining tag of node as expected)
361
						var node = container.lastChild;
362
						var len = 0;
363
						while(node && node.nodeType == 3){
364
							len += node.length;
365
							container = node; //pass through
366
							node = node.previousSibling;
367
						}
368
						if(node){
369
							atmrange.moveToElementText(node);
370
						}
371
						atmrange.collapse(false);
372
						offset = len; //pass through
373
					}else{ //no childNodes
374
						atmrange.moveToElementText(container);
375
						atmrange.collapse(true);
376
					}
377
				}else{
378
					if(offset > 0){
379
						var node = container.childNodes[offset-1];
380
						if(node.nodeType==3){
381
							container = node;
382
							offset = node.length;
383
							//pass through
384
						}else{
385
							atmrange.moveToElementText(node);
386
							atmrange.collapse(false);
387
						}
388
					}
389
				}
390
			}
391
			if(container.nodeType==3){
392
				var prevnodeobj = dijit.range.adjacentNoneTextNode(container);
393
				var prevnode = prevnodeobj[0], len = prevnodeobj[1];
394
				if(prevnode){
395
					atmrange.moveToElementText(prevnode);
396
					atmrange.collapse(false);
397
					//if contentEditable is not inherit, the above collapse won't make the end point
398
					//in the correctly position: it always has a -1 offset, so compensate it
399
					if(prevnode.contentEditable!='inherit'){
400
						len++;
401
					}
402
				}else{
403
					atmrange.moveToElementText(container.parentNode);
404
					atmrange.collapse(true);
405
				}
406
 
407
				offset += len;
408
				if(offset>0){
409
					if(atmrange.moveEnd('character',offset) != offset){
410
						alert('Error when moving!');
411
					}
412
					atmrange.collapse(false);
413
				}
414
			}
415
 
416
			return atmrange;
417
		},
418
		decomposeTextRange: function(range){
419
			var tmpary = dijit.range.ie.getEndPoint(range);
420
			var startContainter = tmpary[0], startOffset = tmpary[1];
421
			var endContainter = tmpary[0], endOffset = tmpary[1];
422
 
423
			if(range.htmlText.length){
424
				if(range.htmlText == range.text){ //in the same text node
425
					endOffset = startOffset+range.text.length;
426
				}else{
427
					tmpary = dijit.range.ie.getEndPoint(range,true);
428
					endContainter = tmpary[0], endOffset = tmpary[1];
429
				}
430
			}
431
			return [[startContainter, startOffset],[endContainter, endOffset], range.parentElement()];
432
		},
433
		setRange: function(range, startContainter,
434
			startOffset, endContainter, endOffset, check){
435
			var startrange = dijit.range.ie.setEndPoint(range, startContainter, startOffset);
436
			range.setEndPoint('StartToStart', startrange);
437
			if(!this.collapsed){
438
				var endrange = dijit.range.ie.setEndPoint(range, endContainter, endOffset);
439
				range.setEndPoint('EndToEnd', endrange);
440
			}
441
 
442
			return range;
443
		}
444
	}
445
 
446
dojo.declare("dijit.range.W3CRange",null, {
447
	constructor: function(){
448
		if(arguments.length>0){
449
			this.setStart(arguments[0][0][0],arguments[0][0][1]);
450
			this.setEnd(arguments[0][1][0],arguments[0][1][1],arguments[0][2]);
451
		}else{
452
			this.commonAncestorContainer = null;
453
			this.startContainer = null;
454
			this.startOffset = 0;
455
			this.endContainer = null;
456
			this.endOffset = 0;
457
			this.collapsed = true;
458
		}
459
	},
460
	_simpleSetEndPoint: function(node, range, end){
461
		var r = (this._body||node.ownerDocument.body).createTextRange();
462
		if(node.nodeType!=1){
463
			r.moveToElementText(node.parentNode);
464
		}else{
465
			r.moveToElementText(node);
466
		}
467
		r.collapse(true);
468
		range.setEndPoint(end?'EndToEnd':'StartToStart',r);
469
	},
470
	_updateInternal: function(__internal_common){
471
		if(this.startContainer !== this.endContainer){
472
			if(!__internal_common){
473
				var r = (this._body||this.startContainer.ownerDocument.body).createTextRange();
474
				this._simpleSetEndPoint(this.startContainer,r);
475
				this._simpleSetEndPoint(this.endContainer,r,true);
476
				__internal_common = r.parentElement();
477
			}
478
			this.commonAncestorContainer = dijit.range.getCommonAncestor(this.startContainer, this.endContainer, __internal_common);
479
		}else{
480
			this.commonAncestorContainer = this.startContainer;
481
		}
482
		this.collapsed = (this.startContainer === this.endContainer) && (this.startOffset == this.endOffset);
483
	},
484
	setStart: function(node, offset, __internal_common){
485
		if(this.startContainer === node && this.startOffset == offset){
486
			return;
487
		}
488
		delete this._cachedBookmark;
489
 
490
		this.startContainer = node;
491
		this.startOffset = offset;
492
		if(!this.endContainer){
493
			this.setEnd(node, offset, __internal_common);
494
		}else{
495
			this._updateInternal(__internal_common);
496
		}
497
	},
498
	setEnd: function(node, offset, __internal_common){
499
		if(this.endContainer === node && this.endOffset == offset){
500
			return;
501
		}
502
		delete this._cachedBookmark;
503
 
504
		this.endContainer = node;
505
		this.endOffset = offset;
506
		if(!this.startContainer){
507
			this.setStart(node, offset, __internal_common);
508
		}else{
509
			this._updateInternal(__internal_common);
510
		}
511
	},
512
	setStartAfter: function(node, offset){
513
		this._setPoint('setStart', node, offset, 1);
514
	},
515
	setStartBefore: function(node, offset){
516
		this._setPoint('setStart', node, offset, 0);
517
	},
518
	setEndAfter: function(node, offset){
519
		this._setPoint('setEnd', node, offset, 1);
520
	},
521
	setEndBefore: function(node, offset){
522
		this._setPoint('setEnd', node, offset, 0);
523
	},
524
	_setPoint: function(what, node, offset, ext){
525
		var index = dijit.range.getIndex(node, node.parentNode).o;
526
		this[what](node.parentNode, index.pop()+ext);
527
	},
528
	_getIERange: function(){
529
		var r=(this._body||this.endContainer.ownerDocument.body).createTextRange();
530
		dijit.range.ie.setRange(r, this.startContainer, this.startOffset, this.endContainer, this.endOffset);
531
		return r;
532
	},
533
	getBookmark: function(body){
534
		this._getIERange();
535
		return this._cachedBookmark;
536
	},
537
	_select: function(){
538
		var r = this._getIERange();
539
		r.select();
540
	},
541
	deleteContents: function(){
542
		var r = this._getIERange();
543
		r.pasteHTML('');
544
		this.endContainer = this.startContainer;
545
		this.endOffset = this.startOffset;
546
		this.collapsed = true;
547
	},
548
	cloneRange: function(){
549
		var r = new dijit.range.W3CRange([[this.startContainer,this.startOffset],
550
			[this.endContainer,this.endOffset]]);
551
		r._body = this._body;
552
		return r;
553
	},
554
	detach: function(){
555
		this._body = null;
556
		this.commonAncestorContainer = null;
557
		this.startContainer = null;
558
		this.startOffset = 0;
559
		this.endContainer = null;
560
		this.endOffset = 0;
561
		this.collapsed = true;
562
}
563
});
564
} //if(!dijit.range._w3c)
565
 
566
}